Merge remote-tracking branch 'bbb/v3.0.x-release' into merge-dec-7

This commit is contained in:
Anton Georgiev 2023-12-07 15:43:32 -05:00
commit 2126bba957
856 changed files with 24500 additions and 21545 deletions

View File

@ -13,6 +13,7 @@ on:
paths-ignore: paths-ignore:
- "docs/**" - "docs/**"
- "**/*.md" - "**/*.md"
- "bigbluebutton-html5/public/locales/*.json"
permissions: permissions:
contents: read contents: read
concurrency: 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 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 - package: bbb-graphql-server
build-name: 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 - package: bbb-etherpad
cache-files-list: bbb-etherpad.placeholder.sh 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 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 build-list: bbb-webrtc-sfu bbb-webrtc-recorder
cache-files-list: bbb-webrtc-sfu.placeholder.sh bbb-webrtc-recorder.placeholder.sh cache-files-list: bbb-webrtc-sfu.placeholder.sh bbb-webrtc-recorder.placeholder.sh
- package: others - 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: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Merge branches - name: Merge branches
@ -252,7 +253,7 @@ jobs:
apt --purge -y remove apache2-bin apt --purge -y remove apache2-bin
' '
- name: Install BBB - name: Install BBB
timeout-minutes: 15 timeout-minutes: 25
run: | run: |
sudo -i <<EOF sudo -i <<EOF
set -e set -e
@ -263,6 +264,12 @@ jobs:
sed -i "s/\"minify\": true,/\"minify\": false,/" /usr/share/etherpad-lite/settings.json sed -i "s/\"minify\": true,/\"minify\": false,/" /usr/share/etherpad-lite/settings.json
bbb-conf --restart bbb-conf --restart
EOF 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 - name: Install test dependencies
working-directory: ./bigbluebutton-tests/playwright working-directory: ./bigbluebutton-tests/playwright
run: | run: |
@ -272,13 +279,18 @@ jobs:
npx playwright install npx playwright install
' '
- name: Run tests - 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: env:
NODE_EXTRA_CA_CERTS: /usr/local/share/ca-certificates/bbb-dev/bbb-dev-ca.crt NODE_EXTRA_CA_CERTS: /usr/local/share/ca-certificates/bbb-dev/bbb-dev-ca.crt
ACTIONS_RUNNER_DEBUG: true ACTIONS_RUNNER_DEBUG: true
BBB_URL: https://bbb-ci.test/bigbluebutton/api BBB_URL: https://bbb-ci.test/bigbluebutton/api
BBB_SECRET: bbbci BBB_SECRET: bbbci
run: npm run test-chromium-ci -- --shard ${{ matrix.shard }}
- name: Run Firefox tests - name: Run Firefox tests
working-directory: ./bigbluebutton-tests/playwright working-directory: ./bigbluebutton-tests/playwright
if: | if: |

View File

@ -12,7 +12,7 @@ stages:
# define which docker image to use for builds # define which docker image to use for builds
default: 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. # 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 # 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: script:
- build/setup-inside-docker.sh bbb-webrtc-recorder - 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: bbb-transcription-controller-build:
extends: .build_job extends: .build_job
script: 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.5.x (or earlier) | :x: |
| 2.6.x   | :white_check_mark: | | 2.6.x   | :white_check_mark: |
| 2.7.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. 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 package org.bigbluebutton.build
import sbt._ import sbt._
import Keys._
object Dependencies { object Dependencies {
@ -37,6 +36,7 @@ object Dependencies {
val scalaTest = "3.2.11" val scalaTest = "3.2.11"
val mockito = "2.23.0" val mockito = "2.23.0"
val akkaTestKit = "2.6.0" val akkaTestKit = "2.6.0"
val jacksonDataFormat = "2.13.5"
} }
object Compile { object Compile {
@ -64,7 +64,11 @@ object Dependencies {
val slick = "com.typesafe.slick" %% "slick" % Versions.slick val slick = "com.typesafe.slick" %% "slick" % Versions.slick
val slickHikaricp = "com.typesafe.slick" %% "slick-hikaricp" % Versions.slick val slickHikaricp = "com.typesafe.slick" %% "slick-hikaricp" % Versions.slick
val slickPg = "com.github.tminglei" %% "slick-pg" % Versions.slickPg 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 postgresql = "org.postgresql" % "postgresql" % Versions.postgresql
val jacksonDataFormat = "com.fasterxml.jackson.dataformat" % "jackson-dataformat-yaml" % Versions.jacksonDataFormat
val snakeYaml = "org.yaml" % "snakeyaml"
} }
object Test { object Test {
@ -101,5 +105,7 @@ object Dependencies {
Compile.slick, Compile.slick,
Compile.slickHikaricp, Compile.slickHikaricp,
Compile.slickPg, 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.core.pubsub.senders.ReceivedJsonMsgHandlerActor
import org.bigbluebutton.core2.AnalyticsActor import org.bigbluebutton.core2.AnalyticsActor
import org.bigbluebutton.core2.FromAkkaAppsMsgSenderActor import org.bigbluebutton.core2.FromAkkaAppsMsgSenderActor
import org.bigbluebutton.endpoint.redis.AppsRedisSubscriberActor import org.bigbluebutton.endpoint.redis.{AppsRedisSubscriberActor, ExportAnnotationsActor, GraphqlActionsActor, LearningDashboardActor, RedisRecorderActor}
import org.bigbluebutton.endpoint.redis.{ RedisRecorderActor, ExportAnnotationsActor }
import org.bigbluebutton.endpoint.redis.LearningDashboardActor
import org.bigbluebutton.common2.bus.IncomingJsonMessageBus import org.bigbluebutton.common2.bus.IncomingJsonMessageBus
import org.bigbluebutton.service.{HealthzService, MeetingInfoActor, MeetingInfoService} import org.bigbluebutton.service.{HealthzService, MeetingInfoActor, MeetingInfoService}
@ -69,6 +67,12 @@ object Boot extends App with SystemConfiguration {
"LearningDashboardActor" "LearningDashboardActor"
) )
val graphqlActionsActor = system.actorOf(
GraphqlActionsActor.props(system),
"GraphqlActionsActor"
)
ClientSettings.loadClientSettingsFromFile()
recordingEventBus.subscribe(redisRecorderActor, outMessageChannel) recordingEventBus.subscribe(redisRecorderActor, outMessageChannel)
val incomingJsonMessageBus = new IncomingJsonMessageBus val incomingJsonMessageBus = new IncomingJsonMessageBus
@ -85,6 +89,9 @@ object Boot extends App with SystemConfiguration {
outBus2.subscribe(learningDashboardActor, outBbbMsgMsgChannel) outBus2.subscribe(learningDashboardActor, outBbbMsgMsgChannel)
bbbMsgBus.subscribe(learningDashboardActor, analyticsChannel) bbbMsgBus.subscribe(learningDashboardActor, analyticsChannel)
eventBus.subscribe(graphqlActionsActor, meetingManagerChannel)
bbbMsgBus.subscribe(graphqlActionsActor, analyticsChannel)
val bbbActor = system.actorOf(BigBlueButtonActor.props(system, eventBus, bbbMsgBus, outGW, healthzService), "bigbluebutton-actor") val bbbActor = system.actorOf(BigBlueButtonActor.props(system, eventBus, bbbMsgBus, outGW, healthzService), "bigbluebutton-actor")
eventBus.subscribe(bbbActor, meetingManagerChannel) eventBus.subscribe(bbbActor, meetingManagerChannel)

View File

@ -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 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 import com.typesafe.config.ConfigFactory
trait SystemConfiguration { trait SystemConfiguration {
@ -77,6 +80,13 @@ trait SystemConfiguration {
lazy val analyticsIncludeChat = Try(config.getBoolean("analytics.includeChat")).getOrElse(true) 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 // Grab the "interface" parameter from the http config
val httpHost = config.getString("http.interface") val httpHost = config.getString("http.interface")
// Grab the "port" parameter from the http config // Grab the "port" parameter from the http config

View File

@ -81,6 +81,8 @@ class BigBlueButtonActor(
case m: GetRunningMeetingsReqMsg => handleGetRunningMeetingsReqMsg(m) case m: GetRunningMeetingsReqMsg => handleGetRunningMeetingsReqMsg(m)
case m: CheckAlivePingSysMsg => handleCheckAlivePingSysMsg(m) case m: CheckAlivePingSysMsg => handleCheckAlivePingSysMsg(m)
case m: ValidateConnAuthTokenSysMsg => handleValidateConnAuthTokenSysMsg(m) case m: ValidateConnAuthTokenSysMsg => handleValidateConnAuthTokenSysMsg(m)
case _: UserGraphqlConnectionStablishedSysMsg => //Ignore
case _: UserGraphqlConnectionClosedSysMsg => //Ignore
case _ => log.warning("Cannot handle " + msg.envelope.name) 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 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 * Sent by breakout room to parent meeting to obtain padId
* @param breakoutId * @param breakoutId

View File

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

View File

@ -2,8 +2,8 @@ package org.bigbluebutton.core.apps.audiocaptions
import org.bigbluebutton.common2.msgs._ import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.bus.MessageBus import org.bigbluebutton.core.bus.MessageBus
import org.bigbluebutton.core.db.AudioCaptionDAO import org.bigbluebutton.core.db.CaptionDAO
import org.bigbluebutton.core.models.AudioCaptions import org.bigbluebutton.core.models.{AudioCaptions, Users2x}
import org.bigbluebutton.core.running.LiveMeeting import org.bigbluebutton.core.running.LiveMeeting
import java.sql.Timestamp import java.sql.Timestamp
@ -66,7 +66,12 @@ trait UpdateTranscriptPubMsgHdlr {
val transcript = AudioCaptions.parseTranscript(msg.body.transcript) 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( broadcastEvent(
msg.header.userId, msg.header.userId,

View File

@ -70,7 +70,7 @@ trait CreateBreakoutRoomsCmdMsgHdlr extends RightsManagementTrait {
breakout.freeJoin, breakout.freeJoin,
liveMeeting.props.voiceProp.dialNumber, liveMeeting.props.voiceProp.dialNumber,
breakout.voiceConf, breakout.voiceConf,
msg.body.durationInMinutes * 60, msg.body.durationInMinutes,
liveMeeting.props.password.moderatorPass, liveMeeting.props.password.moderatorPass,
liveMeeting.props.password.viewerPass, liveMeeting.props.password.viewerPass,
presId, presSlide, msg.body.record, 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.api.{ SendTimeRemainingAuditInternalMsg, UpdateBreakoutRoomTimeInternalMsg }
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait } import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
import org.bigbluebutton.core.bus.BigBlueButtonEvent 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.domain.MeetingState2x
import org.bigbluebutton.core.running.{ MeetingActor, OutMsgRouter } import org.bigbluebutton.core.running.{ MeetingActor, OutMsgRouter }
import org.bigbluebutton.core2.message.senders.{ MsgBuilder, Sender } 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) log.debug("Updating {} minutes for breakout rooms time in meeting {}", msg.body.timeInMinutes, props.meetingProp.intId)
BreakoutRoomDAO.updateRoomsDuration(props.meetingProp.intId, newDurationInSeconds) BreakoutRoomDAO.updateRoomsDuration(props.meetingProp.intId, newDurationInSeconds)
MeetingDAO.updateMeetingDurationByParentMeeting(props.meetingProp.intId, newDurationInSeconds)
breakoutModel.setTime(newDurationInSeconds) breakoutModel.setTime(newDurationInSeconds)
} }
} }

View File

@ -2,6 +2,7 @@ package org.bigbluebutton.core.apps.pads
import org.bigbluebutton.common2.msgs._ import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.bus.MessageBus import org.bigbluebutton.core.bus.MessageBus
import org.bigbluebutton.core.db.CaptionDAO
import org.bigbluebutton.core.models.Pads import org.bigbluebutton.core.models.Pads
import org.bigbluebutton.core.running.LiveMeeting 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) broadcastEditCaptionHistoryEvent(msg.body.userId, msg.body.start, msg.body.end, group.name, locale, msg.body.text)
val tail = liveMeeting.captionModel.getTextTail(group.name) val tail = liveMeeting.captionModel.getTextTail(group.name)
broadcastPadTailEvent(group.externalId, tail) broadcastPadTailEvent(group.externalId, tail)
CaptionDAO.insertOrUpdatePadCaption(liveMeeting.props.meetingProp.intId, locale, msg.body.userId, tail)
} }
} }
case _ => 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.{ PermissionCheck, RightsManagementTrait }
import org.bigbluebutton.core.apps.presentationpod.PresentationSender import org.bigbluebutton.core.apps.presentationpod.PresentationSender
import org.bigbluebutton.core.bus.MessageBus 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.domain.MeetingState2x
import org.bigbluebutton.core.running.LiveMeeting import org.bigbluebutton.core.running.LiveMeeting
import org.bigbluebutton.core.util.RandomStringGenerator import org.bigbluebutton.core.util.RandomStringGenerator
@ -53,12 +53,13 @@ trait MakePresentationDownloadReqMsgHdlr extends RightsManagementTrait {
def buildBroadcastNewPresFileAvailable(newPresFileAvailableMsg: NewPresFileAvailableMsg, liveMeeting: LiveMeeting): BbbCommonEnvCoreMsg = { def buildBroadcastNewPresFileAvailable(newPresFileAvailableMsg: NewPresFileAvailableMsg, liveMeeting: LiveMeeting): BbbCommonEnvCoreMsg = {
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, liveMeeting.props.meetingProp.intId, "not-used") 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 header = BbbClientMsgHeader(NewPresFileAvailableEvtMsg.NAME, liveMeeting.props.meetingProp.intId, "not-used")
val body = NewPresFileAvailableEvtMsgBody( val body = NewPresFileAvailableEvtMsgBody(
annotatedFileURI = newPresFileAvailableMsg.body.annotatedFileURI, annotatedFileURI = newPresFileAvailableMsg.body.annotatedFileURI,
originalFileURI = newPresFileAvailableMsg.body.originalFileURI, originalFileURI = newPresFileAvailableMsg.body.originalFileURI,
convertedFileURI = newPresFileAvailableMsg.body.convertedFileURI, presId = newPresFileAvailableMsg.body.presId, convertedFileURI = newPresFileAvailableMsg.body.convertedFileURI,
presId = newPresFileAvailableMsg.body.presId,
fileStateType = newPresFileAvailableMsg.body.fileStateType fileStateType = newPresFileAvailableMsg.body.fileStateType
) )
val event = NewPresFileAvailableEvtMsg(header, body) val event = NewPresFileAvailableEvtMsg(header, body)
@ -85,11 +86,11 @@ trait MakePresentationDownloadReqMsgHdlr extends RightsManagementTrait {
BbbCommonEnvCoreMsg(envelope, event) 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 routing = collection.immutable.HashMap("sender" -> "bbb-apps-akka")
val envelope = BbbCoreEnvelope(PresentationUploadTokenSysPubMsg.NAME, routing) val envelope = BbbCoreEnvelope(PresentationUploadTokenSysPubMsg.NAME, routing)
val header = BbbClientMsgHeader(PresentationUploadTokenSysPubMsg.NAME, parentId, userId) val header = BbbClientMsgHeader(PresentationUploadTokenSysPubMsg.NAME, parentMeetingId, userId)
val body = PresentationUploadTokenSysPubMsgBody("DEFAULT_PRESENTATION_POD", presentationUploadToken, filename, parentId) val body = PresentationUploadTokenSysPubMsgBody("DEFAULT_PRESENTATION_POD", presentationUploadToken, filename, parentMeetingId, presId)
val event = PresentationUploadTokenSysPubMsg(header, body) val event = PresentationUploadTokenSysPubMsg(header, body)
BbbCommonEnvCoreMsg(envelope, event) BbbCommonEnvCoreMsg(envelope, event)
} }
@ -133,9 +134,18 @@ trait MakePresentationDownloadReqMsgHdlr extends RightsManagementTrait {
val currentPres: Option[PresentationInPod] = presentationPods.flatMap(_.getPresentation(presId)).headOption 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." val reason = "Annotated presentation download disabled for this meeting."
PermissionCheck.ejectUserForFailedPermission(meetingId, userId, reason, bus.outGW, liveMeeting) 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)) { } else if (permissionFailed(PermissionCheck.MOD_LEVEL, PermissionCheck.VIEWER_LEVEL, liveMeeting.users2x, userId)) {
val reason = "No permission to download presentation." val reason = "No permission to download presentation."
PermissionCheck.ejectUserForFailedPermission(meetingId, userId, reason, bus.outGW, liveMeeting) PermissionCheck.ejectUserForFailedPermission(meetingId, userId, reason, bus.outGW, liveMeeting)
@ -196,9 +206,10 @@ trait MakePresentationDownloadReqMsgHdlr extends RightsManagementTrait {
val filename = m.filename val filename = m.filename
val presentationUploadToken: String = PresentationPodsApp.generateToken("DEFAULT_PRESENTATION_POD", userId) 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 // 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")) { if (liveMeeting.props.meetingProp.disabledFeatures.contains("importPresentationWithAnnotationsFromBreakoutRooms")) {
log.error(s"Capturing breakout rooms slides disabled in meeting ${meetingId}.") 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 currentPage: PresentationPage = PresentationInPod.getCurrentPage(currentPres.get).get
val pagesRange: List[Int] = if (allPages) (1 to pageCount).toList else List(currentPage.num) val pagesRange: List[Int] = if (allPages) (1 to pageCount).toList else List(currentPage.num)
val exportJob: ExportJob = 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 storeAnnotationPages: List[PresentationPageForExport] = getPresentationPagesForExport(pagesRange, pageCount, presId, currentPres, liveMeeting);
val annotationCount: Int = storeAnnotationPages.map(_.annotations.size).sum val annotationCount: Int = storeAnnotationPages.map(_.annotations.size).sum
@ -248,8 +259,13 @@ trait MakePresentationDownloadReqMsgHdlr extends RightsManagementTrait {
"filename" -> "annotated_slides.pdf" "filename" -> "annotated_slides.pdf"
) )
ChatMessageDAO.insertSystemMsg(liveMeeting.props.meetingProp.intId, GroupChatApp.MAIN_PUBLIC_CHAT, "", GroupChatMessageType.PRESENTATION, presentationDownloadInfo, "") ChatMessageDAO.insertSystemMsg(liveMeeting.props.meetingProp.intId, GroupChatApp.MAIN_PUBLIC_CHAT, "", GroupChatMessageType.PRESENTATION, presentationDownloadInfo, "")
} else if (m.body.fileStateType == "Converted") {
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)) bus.outGW.send(buildBroadcastNewPresFileAvailable(m, liveMeeting))
} }
@ -265,6 +281,7 @@ trait MakePresentationDownloadReqMsgHdlr extends RightsManagementTrait {
} }
def handle(m: PresAnnStatusMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = { 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)) 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 jobId: String = s"${m.body.breakoutId}-notes" // Used as the temporaryPresentationId upon upload
val filename = m.body.filename val filename = m.body.filename
val presentationUploadToken: String = PresentationPodsApp.generateToken("DEFAULT_PRESENTATION_POD", userId) 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 exportJob = new ExportJob(jobId, JobTypes.CAPTURE_NOTES, filename, m.body.padId, "", true, List(), m.body.parentMeetingId, presentationUploadToken)
val job = buildStoreExportJobInRedisSysMsg(exportJob, liveMeeting) val job = buildStoreExportJobInRedisSysMsg(exportJob, liveMeeting)

View File

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

View File

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

View File

@ -37,10 +37,6 @@ trait PresentationConversionUpdatePubMsgHdlr {
bus.outGW.send(msgEvent) 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) broadcastEvent(msg)
state state
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,6 @@
package org.bigbluebutton.core.apps.presentationpod package org.bigbluebutton.core.apps.presentationpod
import org.apache.commons.codec.digest.DigestUtils
import org.bigbluebutton.common2.domain._ import org.bigbluebutton.common2.domain._
import org.bigbluebutton.core.domain._ import org.bigbluebutton.core.domain._
import org.bigbluebutton.core.models._ import org.bigbluebutton.core.models._
@ -74,7 +75,7 @@ object PresentationPodsApp {
} }
def translatePresentationToPresentationVO(pres: PresentationInPod, temporaryPresentationId: String, def translatePresentationToPresentationVO(pres: PresentationInPod, temporaryPresentationId: String,
isInitialPresentation: Boolean, filenameConverted: String): PresentationVO = { defaultPresentation: Boolean, filenameConverted: String): PresentationVO = {
val pages = pres.pages.values.map { page => val pages = pres.pages.values.map { page =>
PageVO( PageVO(
id = page.id, id = page.id,
@ -92,7 +93,7 @@ object PresentationPodsApp {
) )
} }
PresentationVO(pres.id, temporaryPresentationId, pres.name, pres.current, pages.toVector, pres.downloadable, 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] = { def setCurrentPresentationInPod(state: MeetingState2x, podId: String, nextCurrentPresId: String): Option[PresentationPod] = {
@ -107,5 +108,11 @@ object PresentationPodsApp {
def generateToken(podId: String, userId: String): String = { def generateToken(podId: String, userId: String): String = {
"PresUploadToken-" + RandomStringGenerator.randomAlphanumericString(8) + podId + "-" + userId "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 package org.bigbluebutton.core.apps.presentationpod
import org.apache.commons.codec.digest.DigestUtils
import org.bigbluebutton.common2.msgs._ import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.bus.MessageBus import org.bigbluebutton.core.bus.MessageBus
import org.bigbluebutton.core.domain.MeetingState2x import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.models.Users2x import org.bigbluebutton.core.models.Users2x
import org.bigbluebutton.core.running.LiveMeeting import org.bigbluebutton.core.running.LiveMeeting
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait } import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
import org.bigbluebutton.core.db.PresPresentationDAO
import org.bigbluebutton.core.util.RandomStringGenerator
trait PresentationUploadTokenReqMsgHdlr extends RightsManagementTrait { trait PresentationUploadTokenReqMsgHdlr extends RightsManagementTrait {
this: PresentationPodHdlrs => this: PresentationPodHdlrs =>
@ -13,13 +16,13 @@ trait PresentationUploadTokenReqMsgHdlr extends RightsManagementTrait {
def handle(msg: PresentationUploadTokenReqMsg, state: MeetingState2x, def handle(msg: PresentationUploadTokenReqMsg, state: MeetingState2x,
liveMeeting: LiveMeeting, bus: MessageBus): 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 // send back to client
val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, liveMeeting.props.meetingProp.intId, msg.header.userId) val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, liveMeeting.props.meetingProp.intId, msg.header.userId)
val envelope = BbbCoreEnvelope(PresentationUploadTokenPassRespMsg.NAME, routing) val envelope = BbbCoreEnvelope(PresentationUploadTokenPassRespMsg.NAME, routing)
val header = BbbClientMsgHeader(PresentationUploadTokenPassRespMsg.NAME, liveMeeting.props.meetingProp.intId, msg.header.userId) 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 event = PresentationUploadTokenPassRespMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event) val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
bus.outGW.send(msgEvent) bus.outGW.send(msgEvent)
@ -37,13 +40,13 @@ trait PresentationUploadTokenReqMsgHdlr extends RightsManagementTrait {
bus.outGW.send(msgEvent) bus.outGW.send(msgEvent)
} }
def broadcastPresentationUploadTokenSysPubMsg(msg: PresentationUploadTokenReqMsg, token: String): Unit = { def broadcastPresentationUploadTokenSysPubMsg(msg: PresentationUploadTokenReqMsg, token: String, presId: String): Unit = {
// send to bbb-web // send to bbb-web
val routing = collection.immutable.HashMap("sender" -> "bbb-apps-akka") val routing = collection.immutable.HashMap("sender" -> "bbb-apps-akka")
val envelope = BbbCoreEnvelope(PresentationUploadTokenSysPubMsg.NAME, routing) val envelope = BbbCoreEnvelope(PresentationUploadTokenSysPubMsg.NAME, routing)
val header = BbbClientMsgHeader(PresentationUploadTokenSysPubMsg.NAME, liveMeeting.props.meetingProp.intId, msg.header.userId) 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 event = PresentationUploadTokenSysPubMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event) val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
@ -68,9 +71,10 @@ trait PresentationUploadTokenReqMsgHdlr extends RightsManagementTrait {
log.info("handlePresentationUploadTokenReqMsg" + liveMeeting.props.meetingProp.intId + log.info("handlePresentationUploadTokenReqMsg" + liveMeeting.props.meetingProp.intId +
" userId=" + msg.header.userId + " filename=" + msg.body.filename) " userId=" + msg.header.userId + " filename=" + msg.body.filename)
val meetingId = liveMeeting.props.meetingProp.intId
if (liveMeeting.props.meetingProp.disabledFeatures.contains("presentation")) { if (liveMeeting.props.meetingProp.disabledFeatures.contains("presentation")) {
broadcastPresentationUploadTokenFailResp(msg) broadcastPresentationUploadTokenFailResp(msg)
val meetingId = liveMeeting.props.meetingProp.intId
val reason = "Presentation is disabled for this meeting" val reason = "Presentation is disabled for this meeting"
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting) PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting)
} else if (filterPresentationMessage(liveMeeting.users2x, msg.header.userId) && } else if (filterPresentationMessage(liveMeeting.users2x, msg.header.userId) &&
@ -81,8 +85,19 @@ trait PresentationUploadTokenReqMsgHdlr extends RightsManagementTrait {
} else { } else {
if (userIsAllowedToUploadInPod(msg.body.podId, msg.header.userId)) { if (userIsAllowedToUploadInPod(msg.body.podId, msg.header.userId)) {
val token = PresentationPodsApp.generateToken(msg.body.podId, msg.header.userId) val token = PresentationPodsApp.generateToken(msg.body.podId, msg.header.userId)
broadcastPresentationUploadTokenPassResp(msg, token) val presentationId = PresentationPodsApp.generatePresentationId(msg.body.filename)
broadcastPresentationUploadTokenSysPubMsg(msg, token) broadcastPresentationUploadTokenPassResp(msg, token, presentationId)
broadcastPresentationUploadTokenSysPubMsg(msg, token, presentationId)
PresPresentationDAO.insertToken(
meetingId,
msg.header.userId,
msg.body.uploadTemporaryId,
presentationId,
token,
msg.body.filename
)
} else { } else {
broadcastPresentationUploadTokenFailResp(msg) broadcastPresentationUploadTokenFailResp(msg)
} }

View File

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

View File

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

View File

@ -3,6 +3,7 @@ package org.bigbluebutton.core.apps.presentationpod
import org.bigbluebutton.common2.msgs._ import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait } import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
import org.bigbluebutton.core.bus.MessageBus import org.bigbluebutton.core.bus.MessageBus
import org.bigbluebutton.core.db.PresPresentationDAO
import org.bigbluebutton.core.domain.MeetingState2x import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.running.LiveMeeting import org.bigbluebutton.core.running.LiveMeeting
@ -17,8 +18,21 @@ trait SetPresentationDownloadablePubMsgHdlr extends RightsManagementTrait {
val meetingId = liveMeeting.props.meetingProp.intId val meetingId = liveMeeting.props.meetingProp.intId
if (filterPresentationMessage(liveMeeting.users2x, msg.header.userId) && if (filterPresentationMessage(liveMeeting.users2x, msg.header.userId) &&
permissionFailed(PermissionCheck.GUEST_LEVEL, PermissionCheck.PRESENTER_LEVEL, liveMeeting.users2x, msg.header.userId)) { permissionFailed(
val reason = "No permission to remove presentation from meeting." 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) PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting)
state state
} else { } else {
@ -37,7 +51,9 @@ trait SetPresentationDownloadablePubMsgHdlr extends RightsManagementTrait {
PresentationSender.broadcastSetPresentationDownloadableEvtMsg(bus, meetingId, pod.id, PresentationSender.broadcastSetPresentationDownloadableEvtMsg(bus, meetingId, pod.id,
msg.header.userId, presentationId, downloadable, pres.name, downloadableExtension) 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) 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 { } else {
if (TimerModel.getStopwatch(liveMeeting.timerModel) != msg.body.stopwatch) { if (TimerModel.getStopwatch(liveMeeting.timerModel) != msg.body.stopwatch) {
TimerModel.setStopwatch(liveMeeting.timerModel, msg.body.stopwatch) TimerModel.setStopwatch(liveMeeting.timerModel, msg.body.stopwatch)
TimerModel.reset(liveMeeting.timerModel) //Reset on switch Stopwatch/Timer
if (msg.body.stopwatch) { if (msg.body.stopwatch) {
TimerModel.setTrack(liveMeeting.timerModel, "noTrack") TimerModel.setTrack(liveMeeting.timerModel, "noTrack")
} }

View File

@ -2,8 +2,11 @@ package org.bigbluebutton.core.apps.users
import org.bigbluebutton.common2.msgs._ import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.apps.RightsManagementTrait import org.bigbluebutton.core.apps.RightsManagementTrait
import org.bigbluebutton.core.models.{ UserState, Users2x } import org.bigbluebutton.core.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.core.running.{ LiveMeeting, OutMsgRouter }
import org.bigbluebutton.core2.MeetingStatus2x
import org.bigbluebutton.core2.message.senders.MsgBuilder import org.bigbluebutton.core2.message.senders.MsgBuilder
trait ChangeUserAwayReqMsgHdlr extends RightsManagementTrait { trait ChangeUserAwayReqMsgHdlr extends RightsManagementTrait {
@ -30,6 +33,8 @@ trait ChangeUserAwayReqMsgHdlr extends RightsManagementTrait {
outGW.send(msgEventChange) outGW.send(msgEventChange)
} }
val permissions = MeetingStatus2x.getPermissions(liveMeeting.status)
for { for {
user <- Users2x.findWithIntId(liveMeeting.users2x, msg.body.userId) user <- Users2x.findWithIntId(liveMeeting.users2x, msg.body.userId)
newUserState <- Users2x.setUserAway(liveMeeting.users2x, user.intId, msg.body.away) 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")) 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) broadcast(newUserState, msg.body.away)
} }
} }

View File

@ -60,7 +60,8 @@ trait RegisterUserReqMsgHdlr {
val regUser = RegisteredUsers.create(msg.body.intUserId, msg.body.extUserId, val regUser = RegisteredUsers.create(msg.body.intUserId, msg.body.extUserId,
msg.body.name, msg.body.role, msg.body.authToken, msg.body.sessionToken, 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) checkUserConcurrentAccesses(regUser)
RegisteredUsers.add(liveMeeting.registeredUsers, regUser, liveMeeting.props.meetingProp.intId) 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.common2.msgs.UserJoinMeetingReqMsg
import org.bigbluebutton.core.apps.breakout.BreakoutHdlrHelpers 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.domain.MeetingState2x
import org.bigbluebutton.core.models.{ RegisteredUser, RegisteredUsers, Users2x, VoiceUsers } import org.bigbluebutton.core.models._
import org.bigbluebutton.core.running.{ HandlerHelpers, LiveMeeting, MeetingActor, OutMsgRouter } import org.bigbluebutton.core.running._
import org.bigbluebutton.core2.message.senders.MsgBuilder import org.bigbluebutton.core2.message.senders._
trait UserJoinMeetingReqMsgHdlr extends HandlerHelpers { trait UserJoinMeetingReqMsgHdlr extends HandlerHelpers {
this: MeetingActor => this: MeetingActor =>
val liveMeeting: LiveMeeting val liveMeeting: LiveMeeting
val outGW: OutMsgRouter 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) log.info("Received user joined meeting. user {} meetingId={}", msg.body.userId, msg.header.meetingId)
Users2x.findWithIntId(liveMeeting.users2x, msg.body.userId) match { Users2x.findWithIntId(liveMeeting.users2x, msg.body.userId) match {
case Some(reconnectingUser) => case Some(user) => handleUserReconnecting(user, msg, state)
if (reconnectingUser.userLeftFlag.left) { 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) log.info("Resetting flag that user left meeting. user {}", msg.body.userId)
// User has reconnected. Just reset it's flag. ralam Oct 23, 2018
sendUserLeftFlagUpdatedEvtMsg(outGW, liveMeeting, msg.body.userId, false) sendUserLeftFlagUpdatedEvtMsg(outGW, liveMeeting, msg.body.userId, false)
Users2x.resetUserLeftFlag(liveMeeting.users2x, msg.body.userId) Users2x.resetUserLeftFlag(liveMeeting.users2x, msg.body.userId)
} }
state private def validateMaxParticipants(regUser: RegisteredUser): Either[(String, String), Unit] = {
case None => val userHasJoinedAlready = RegisteredUsers.checkUserExtIdHasJoined(regUser.externId, liveMeeting.registeredUsers)
// Check if maxParticipants has been reached val maxParticipants = liveMeeting.props.usersProp.maxUsers - 1
// User are able to reenter if he already joined previously with the same extId
val userHasJoinedAlready = RegisteredUsers.findWithUserId(msg.body.userId, liveMeeting.registeredUsers) match { if (maxParticipants > 0 && //0 = no limit
case Some(regUser: RegisteredUser) => RegisteredUsers.checkUserExtIdHasJoined(regUser.externId, liveMeeting.registeredUsers) RegisteredUsers.numUniqueJoinedUsers(liveMeeting.registeredUsers) >= maxParticipants &&
case None => false !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) { private def checkIfUserGuestStatusIsAllowed(user: RegisteredUser): Either[(String, String), Unit] = {
val newState = userJoinMeeting(outGW, msg.body.authToken, msg.body.clientType, liveMeeting, state) 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) { if (liveMeeting.props.meetingProp.isBreakout) {
BreakoutHdlrHelpers.updateParentMeetingWithUsers(liveMeeting, eventBus) BreakoutHdlrHelpers.updateParentMeetingWithUsers(liveMeeting, eventBus)
} }
}
// Warn previous users that someone connected with same Id private def notifyPreviousUsersWithSameExtId(regUser: RegisteredUser) = {
for {
regUser <- RegisteredUsers.getRegisteredUserWithToken(msg.body.authToken, msg.body.userId,
liveMeeting.registeredUsers)
} yield {
RegisteredUsers.findAllWithExternUserId(regUser.externId, liveMeeting.registeredUsers) RegisteredUsers.findAllWithExternUserId(regUser.externId, liveMeeting.registeredUsers)
.filter(u => u.id != regUser.id) .filter(_.id != regUser.id)
.foreach { previousUser => .foreach { previousUser =>
sendUserConnectedNotification(previousUser, regUser, liveMeeting)
}
}
private def sendUserConnectedNotification(previousUser: RegisteredUser, newUser: RegisteredUser, liveMeeting: LiveMeeting) = {
val notifyUserEvent = MsgBuilder.buildNotifyUserInMeetingEvtMsg( val notifyUserEvent = MsgBuilder.buildNotifyUserInMeetingEvtMsg(
previousUser.id, previousUser.id,
liveMeeting.props.meetingProp.intId, liveMeeting.props.meetingProp.intId,
@ -60,22 +134,19 @@ trait UserJoinMeetingReqMsgHdlr extends HandlerHelpers {
"promote", "promote",
"app.mobileAppModal.userConnectedWithSameId", "app.mobileAppModal.userConnectedWithSameId",
"Notification to warn that user connect again from other browser/device", "Notification to warn that user connect again from other browser/device",
Vector(regUser.name) Vector(newUser.name)
) )
outGW.send(notifyUserEvent) outGW.send(notifyUserEvent)
} }
}
private def clearCachedVoiceUser(regUser: RegisteredUser) =
// fresh user joined (not due to reconnection). Clear (pop) the cached voice user // fresh user joined (not due to reconnection). Clear (pop) the cached voice user
VoiceUsers.recoverVoiceUser(liveMeeting.voiceUsers, msg.body.userId) VoiceUsers.recoverVoiceUser(liveMeeting.voiceUsers, regUser.id)
UserStateDAO.updateExpired(msg.body.userId, false)
newState private def clearExpiredUserState(regUser: RegisteredUser) =
} else { UserStateDAO.updateExpired(regUser.id, false)
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 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.domain.MeetingState2x
import org.bigbluebutton.core.models.{ RegisteredUsers, Users2x } import org.bigbluebutton.core.models.{ RegisteredUsers, Users2x }
import org.bigbluebutton.core.running.{ HandlerHelpers, MeetingActor, OutMsgRouter } import org.bigbluebutton.core.running.{ HandlerHelpers, MeetingActor, OutMsgRouter }
import org.bigbluebutton.core2.message.senders.Sender
trait UserLeaveReqMsgHdlr extends HandlerHelpers { trait UserLeaveReqMsgHdlr extends HandlerHelpers {
this: MeetingActor => this: MeetingActor =>
@ -30,6 +31,7 @@ trait UserLeaveReqMsgHdlr extends HandlerHelpers {
ru <- RegisteredUsers.findWithUserId(msg.body.userId, liveMeeting.registeredUsers) ru <- RegisteredUsers.findWithUserId(msg.body.userId, liveMeeting.registeredUsers)
} yield { } yield {
RegisteredUsers.setUserLoggedOutFlag(liveMeeting.registeredUsers, ru) RegisteredUsers.setUserLoggedOutFlag(liveMeeting.registeredUsers, ru)
Sender.sendInvalidateUserGraphqlConnectionSysMsg(liveMeeting.props.meetingProp.intId, ru.id, ru.sessionToken, "user_loggedout", outGW)
} }
} }
state state

View File

@ -2,12 +2,14 @@ package org.bigbluebutton.core.apps.users
import org.apache.pekko.actor.ActorContext import org.apache.pekko.actor.ActorContext
import org.apache.pekko.event.Logging import org.apache.pekko.event.Logging
import org.bigbluebutton.Boot.eventBus
import org.bigbluebutton.common2.msgs._ import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.api.{SetPresenterInDefaultPodInternalMsg}
import org.bigbluebutton.core.apps.ExternalVideoModel 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.models._
import org.bigbluebutton.core.running.{LiveMeeting, OutMsgRouter} 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.apps.screenshare.ScreenshareApp2x
import org.bigbluebutton.core.db.UserStateDAO import org.bigbluebutton.core.db.UserStateDAO
@ -67,8 +69,8 @@ object UsersApp {
moderator <- Users2x.findModerator(liveMeeting.users2x) moderator <- Users2x.findModerator(liveMeeting.users2x)
newPresenter <- Users2x.makePresenter(liveMeeting.users2x, moderator.intId) newPresenter <- Users2x.makePresenter(liveMeeting.users2x, moderator.intId)
} yield { } yield {
// println(s"automaticallyAssignPresenter: moderator=${moderator} newPresenter=${newPresenter.intId}");
sendPresenterAssigned(outGW, meetingId, newPresenter.intId, newPresenter.name, newPresenter.intId) sendPresenterAssigned(outGW, meetingId, newPresenter.intId, newPresenter.name, newPresenter.intId)
sendPresenterInPodReq(meetingId, newPresenter.intId)
} }
} }
@ -77,6 +79,10 @@ object UsersApp {
outGW.send(event) outGW.send(event)
} }
def sendPresenterInPodReq(meetingId: String, newPresenterIntId: String): Unit = {
eventBus.publish(BigBlueButtonEvent(meetingId, SetPresenterInDefaultPodInternalMsg(newPresenterIntId)))
}
def sendUserLeftMeetingToAllClients(outGW: OutMsgRouter, meetingId: String, def sendUserLeftMeetingToAllClients(outGW: OutMsgRouter, meetingId: String,
userId: String, eject: Boolean = false, ejectedBy: String = "", reason: String = "", reasonCode: String = ""): Unit = { userId: String, eject: Boolean = false, ejectedBy: String = "", reason: String = "", reasonCode: String = ""): Unit = {
// send a user left event for the clients to update // 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.common2.msgs._
import org.bigbluebutton.core.bus.InternalEventBus import org.bigbluebutton.core.bus.InternalEventBus
import org.bigbluebutton.core.db.UserDAO
import org.bigbluebutton.core.domain.MeetingState2x import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.models._ import org.bigbluebutton.core.models._
import org.bigbluebutton.core.running.{ HandlerHelpers, LiveMeeting, OutMsgRouter } import org.bigbluebutton.core.running.{ HandlerHelpers, LiveMeeting, OutMsgRouter }
@ -15,103 +16,79 @@ trait ValidateAuthTokenReqMsgHdlr extends HandlerHelpers {
val eventBus: InternalEventBus val eventBus: InternalEventBus
def handleValidateAuthTokenReqMsg(msg: ValidateAuthTokenReqMsg, state: MeetingState2x): MeetingState2x = { 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." val regUser = RegisteredUsers.getRegisteredUserWithToken(msg.body.authToken, msg.body.userId, liveMeeting.registeredUsers)
var failReasonCode = EjectReasonCode.VALIDATE_TOKEN log.info(s"Number of registered users [${RegisteredUsers.numRegisteredUsers(liveMeeting.registeredUsers)}]")
log.info("Number of registered users [{}]", RegisteredUsers.numRegisteredUsers(liveMeeting.registeredUsers)) regUser.fold {
val regUser = RegisteredUsers.getRegisteredUserWithToken(msg.body.authToken, msg.body.userId, sendFailedValidateAuthTokenRespMsg(msg, "Invalid auth token.", EjectReasonCode.VALIDATE_TOKEN)
liveMeeting.registeredUsers) } { user =>
regUser match { val validationResult = for {
case Some(u) => _ <- checkIfUserGuestStatusIsAllowed(user)
// Check if maxParticipants has been reached _ <- checkIfUserIsBanned(user)
// User are able to reenter if he already joined previously with the same extId _ <- checkIfUserLoggedOut(user)
val hasReachedMaxParticipants = liveMeeting.props.usersProp.maxUsers > 0 && _ <- 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.numUniqueJoinedUsers(liveMeeting.registeredUsers) >= liveMeeting.props.usersProp.maxUsers &&
RegisteredUsers.checkUserExtIdHasJoined(u.externId, liveMeeting.registeredUsers) == false RegisteredUsers.checkUserExtIdHasJoined(user.externId, liveMeeting.registeredUsers) == false) {
Left(("The maximum number of participants allowed for this meeting has been reached.", EjectReasonCode.MAX_PARTICIPANTS))
// 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)
} else { } else {
if (u.banned) { Right(())
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
)
} }
} }
def validateTokenFailed( private def checkIfUserGuestStatusIsAllowed(user: RegisteredUser): Either[(String, String), Unit] = {
outGW: OutMsgRouter, if (user.guestStatus != GuestStatus.ALLOW) {
meetingId: String, Left(("User is not allowed to join", EjectReasonCode.PERMISSION_FAILED))
userId: String, } else {
authToken: String, Right(())
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
} }
def sendValidateAuthTokenRespMsg(meetingId: String, userId: String, authToken: String, private def checkIfUserIsBanned(user: RegisteredUser): Either[(String, String), Unit] = {
valid: Boolean, waitForApproval: Boolean, registeredOn: Long, authTokenValidatedOn: Long, if (user.banned) {
reasonCode: String = EjectReasonCode.NOT_EJECT, reason: String = "User not ejected"): Unit = { Left(("Banned user rejoining", EjectReasonCode.BANNED_USER_REJOINING))
val event = MsgBuilder.buildValidateAuthTokenRespMsg(meetingId, userId, authToken, valid, waitForApproval, registeredOn, } else {
authTokenValidatedOn, reasonCode, reason) 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) outGW.send(event)
} }
def userValidated(user: RegisteredUser, state: MeetingState2x): MeetingState2x = { def sendSuccessfulValidateAuthTokenRespMsg(user: RegisteredUser) = {
val meetingId = liveMeeting.props.meetingProp.intId val meetingId = liveMeeting.props.meetingProp.intId
val updatedUser = RegisteredUsers.updateUserLastAuthTokenValidated(liveMeeting.registeredUsers, user) val updatedUser = RegisteredUsers.updateUserLastAuthTokenValidated(liveMeeting.registeredUsers, user)
sendValidateAuthTokenRespMsg(meetingId, updatedUser.id, updatedUser.authToken, valid = true, waitForApproval = false, updatedUser.registeredOn, updatedUser.lastAuthTokenValidatedOn) val event = MsgBuilder.buildValidateAuthTokenRespMsg(meetingId, updatedUser.id, updatedUser.authToken, true, false, updatedUser.registeredOn,
state updatedUser.lastAuthTokenValidatedOn, EjectReasonCode.NOT_EJECT, "User not ejected")
outGW.send(event)
} }
def sendAllUsersInMeeting(requesterId: String): Unit = { def sendAllUsersInMeeting(requesterId: String): Unit = {

View File

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

View File

@ -4,6 +4,7 @@ import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.models._ import org.bigbluebutton.core.models._
import org.bigbluebutton.core.apps.users.UsersApp import org.bigbluebutton.core.apps.users.UsersApp
import org.bigbluebutton.core.apps.breakout.BreakoutHdlrHelpers import org.bigbluebutton.core.apps.breakout.BreakoutHdlrHelpers
import org.bigbluebutton.core.db.UserDAO
import org.bigbluebutton.core.running.{ LiveMeeting, MeetingActor, OutMsgRouter } import org.bigbluebutton.core.running.{ LiveMeeting, MeetingActor, OutMsgRouter }
trait UserLeftVoiceConfEvtMsgHdlr { trait UserLeftVoiceConfEvtMsgHdlr {
@ -39,6 +40,7 @@ trait UserLeftVoiceConfEvtMsgHdlr {
UsersApp.guestWaitingLeft(liveMeeting, user.intId, outGW) UsersApp.guestWaitingLeft(liveMeeting, user.intId, outGW)
} }
Users2x.remove(liveMeeting.users2x, user.intId) Users2x.remove(liveMeeting.users2x, user.intId)
UserDAO.delete(user.intId)
VoiceApp.removeUserFromVoiceConf(liveMeeting, outGW, msg.body.voiceUserId) 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.running.{LiveMeeting, MeetingActor, OutMsgRouter}
import org.bigbluebutton.core.models._ import org.bigbluebutton.core.models._
import org.bigbluebutton.core.apps.users.UsersApp 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.ColorPicker
import org.bigbluebutton.core.util.TimeUtil import org.bigbluebutton.core.util.TimeUtil
import scala.collection.immutable.Map import scala.collection.immutable.Map
import scala.concurrent.duration._ import scala.concurrent.duration._
@ -323,6 +325,8 @@ object VoiceApp extends SystemConfiguration {
uuid uuid
) )
VoiceUsers.add(liveMeeting.voiceUsers, voiceUserState) VoiceUsers.add(liveMeeting.voiceUsers, voiceUserState)
UserVoiceDAO.update(voiceUserState)
UserDAO.updateVoiceUserJoined(voiceUserState)
broadcastEvent(voiceUserState) broadcastEvent(voiceUserState)

View File

@ -1,7 +1,7 @@
package org.bigbluebutton.core.apps.voice package org.bigbluebutton.core.apps.voice
import org.bigbluebutton.common2.msgs.{ BbbClientMsgHeader, BbbCommonEnvCoreMsg, BbbCoreEnvelope, MessageTypes, Routing, VoiceCallStateEvtMsg, VoiceCallStateEvtMsgBody, VoiceConfCallStateEvtMsg } 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 } import org.bigbluebutton.core.running.{ LiveMeeting, MeetingActor, OutMsgRouter }
trait VoiceConfCallStateEvtMsgHdlr { trait VoiceConfCallStateEvtMsgHdlr {
@ -38,5 +38,9 @@ trait VoiceConfCallStateEvtMsgHdlr {
val event = VoiceCallStateEvtMsg(header, body) val event = VoiceCallStateEvtMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event) val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
outGW.send(msgEvent) 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.common2.msgs._
import org.bigbluebutton.core.apps.PermissionCheck import org.bigbluebutton.core.apps.PermissionCheck
import org.bigbluebutton.core.bus.MessageBus import org.bigbluebutton.core.bus.MessageBus
import org.bigbluebutton.core.db.MeetingUsersPoliciesDAO
import org.bigbluebutton.core.running.LiveMeeting import org.bigbluebutton.core.running.LiveMeeting
import org.bigbluebutton.core2.message.senders.{ MsgBuilder } import org.bigbluebutton.core2.message.senders.MsgBuilder
trait UpdateWebcamsOnlyForModeratorCmdMsgHdlr { trait UpdateWebcamsOnlyForModeratorCmdMsgHdlr {
this: WebcamApp2x => this: WebcamApp2x =>
@ -50,6 +51,8 @@ trait UpdateWebcamsOnlyForModeratorCmdMsgHdlr {
case Some(value) => { case Some(value) => {
log.info(s"Change webcams only for moderator status. meetingId=${meetingId} value=${value}") log.info(s"Change webcams only for moderator status. meetingId=${meetingId} value=${value}")
MeetingUsersPoliciesDAO.updateWebcamsOnlyForModerator(meetingId, msg.body.webcamsOnlyForModerator)
if (value) { if (value) {
val notifyEvent = MsgBuilder.buildNotifyAllInMeetingEvtMsg( val notifyEvent = MsgBuilder.buildNotifyAllInMeetingEvtMsg(
meetingId, meetingId,

View File

@ -38,7 +38,16 @@ trait DeleteWhiteboardAnnotationsPubMsgHdlr extends RightsManagementTrait {
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting) PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting)
} }
} else { } 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) { if (!deletedAnnotations.isEmpty) {
broadcastEvent(msg, deletedAnnotations) 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( DatabaseConnection.db.run(
TableQuery[BreakoutRoomDbTableDef] TableQuery[BreakoutRoomDbTableDef]
.filter(_.parentMeetingId === meetingId) .filter(_.parentMeetingId === parentMeetingId)
.filter(_.endedAt.isEmpty)
.map(u => u.durationInSeconds) .map(u => u.durationInSeconds)
.update(newDurationInSeconds) .update(newDurationInSeconds)
).onComplete { ).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, chatId: String,
meetingId: String, meetingId: String,
correlationId: String, correlationId: String,
createdTime: Long, createdAt: java.sql.Timestamp,
chatEmphasizedText: Boolean, chatEmphasizedText: Boolean,
message: String, message: String,
messageType: String, messageType: String,
@ -26,7 +26,7 @@ class ChatMessageDbTableDef(tag: Tag) extends Table[ChatMessageDbModel](tag, Non
val chatId = column[String]("chatId") val chatId = column[String]("chatId")
val meetingId = column[String]("meetingId") val meetingId = column[String]("meetingId")
val correlationId = column[String]("correlationId") val correlationId = column[String]("correlationId")
val createdTime = column[Long]("createdTime") val createdAt = column[java.sql.Timestamp]("createdAt")
val chatEmphasizedText = column[Boolean]("chatEmphasizedText") val chatEmphasizedText = column[Boolean]("chatEmphasizedText")
val message = column[String]("message") val message = column[String]("message")
val messageType = column[String]("messageType") 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 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) // 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 { object ChatMessageDAO {
@ -49,7 +49,7 @@ object ChatMessageDAO {
chatId = chatId, chatId = chatId,
meetingId = meetingId, meetingId = meetingId,
correlationId = groupChatMessage.correlationId, correlationId = groupChatMessage.correlationId,
createdTime = groupChatMessage.timestamp, createdAt = new java.sql.Timestamp(System.currentTimeMillis()),
chatEmphasizedText = groupChatMessage.chatEmphasizedText, chatEmphasizedText = groupChatMessage.chatEmphasizedText,
message = groupChatMessage.message, message = groupChatMessage.message,
messageType = "default", messageType = "default",
@ -78,11 +78,11 @@ object ChatMessageDAO {
chatId = chatId, chatId = chatId,
meetingId = meetingId, meetingId = meetingId,
correlationId = "", correlationId = "",
createdTime = System.currentTimeMillis(), createdAt = new java.sql.Timestamp(System.currentTimeMillis()),
chatEmphasizedText = false, chatEmphasizedText = false,
message = message, message = message,
messageType = messageType, messageType = messageType,
messageMetadata = Some(JsonUtils.mapToJson(messageMetadata)), messageMetadata = Some(JsonUtils.mapToJson(messageMetadata).compactPrint),
senderId = None, senderId = None,
senderName = senderName, senderName = senderName,
senderRole = None senderRole = None

View File

@ -11,8 +11,9 @@ case class ChatUserDbModel(
chatId: String, chatId: String,
meetingId: String, meetingId: String,
userId: String, userId: String,
lastSeenAt: Long, lastSeenAt: Option[java.sql.Timestamp],
typingAt: Option[java.sql.Timestamp], startedTypingAt: Option[java.sql.Timestamp],
lastTypingAt: Option[java.sql.Timestamp],
visible: Boolean visible: Boolean
) )
@ -20,13 +21,14 @@ class ChatUserDbTableDef(tag: Tag) extends Table[ChatUserDbModel](tag, None, "ch
val chatId = column[String]("chatId", O.PrimaryKey) val chatId = column[String]("chatId", O.PrimaryKey)
val meetingId = column[String]("meetingId", O.PrimaryKey) val meetingId = column[String]("meetingId", O.PrimaryKey)
val userId = column[String]("userId", O.PrimaryKey) val userId = column[String]("userId", O.PrimaryKey)
val lastSeenAt = column[Long]("lastSeenAt") val lastSeenAt = column[Option[java.sql.Timestamp]]("lastSeenAt")
val typingAt = column[Option[java.sql.Timestamp]]("typingAt") val startedTypingAt = column[Option[java.sql.Timestamp]]("startedTypingAt")
val lastTypingAt = column[Option[java.sql.Timestamp]]("lastTypingAt")
val visible = column[Boolean]("visible") 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 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) // 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 { object ChatUserDAO {
@ -46,8 +48,9 @@ object ChatUserDAO {
userId = userId, userId = userId,
chatId = chatId, chatId = chatId,
meetingId = meetingId, meetingId = meetingId,
lastSeenAt = 0, lastSeenAt = None,
typingAt = None, startedTypingAt = None,
lastTypingAt = None,
visible = visible visible = visible
) )
) )
@ -63,11 +66,11 @@ object ChatUserDAO {
.filter(_.meetingId === meetingId) .filter(_.meetingId === meetingId)
.filter(_.chatId === (if (chatId == "public") "MAIN-PUBLIC-GROUP-CHAT" else chatId)) .filter(_.chatId === (if (chatId == "public") "MAIN-PUBLIC-GROUP-CHAT" else chatId))
.filter(_.userId === userId) .filter(_.userId === userId)
.map(u => (u.typingAt)) .map(u => (u.lastTypingAt))
.update(Some(new java.sql.Timestamp(System.currentTimeMillis()))) .update(Some(new java.sql.Timestamp(System.currentTimeMillis())))
).onComplete { ).onComplete {
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated typingAt on chat_user table!") 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 typingAt on chat_user table: $e") 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, presentationUploadExternalUrl: String,
learningDashboardAccessToken: String, learningDashboardAccessToken: String,
createdTime: Long, createdTime: Long,
duration: Int durationInSeconds: Int
) )
class MeetingDbTableDef(tag: Tag) extends Table[MeetingDbModel](tag, None, "meeting") { 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, presentationUploadExternalUrl,
learningDashboardAccessToken, learningDashboardAccessToken,
createdTime, createdTime,
duration durationInSeconds
) <> (MeetingDbModel.tupled, MeetingDbModel.unapply) ) <> (MeetingDbModel.tupled, MeetingDbModel.unapply)
val meetingId = column[String]("meetingId", O.PrimaryKey) val meetingId = column[String]("meetingId", O.PrimaryKey)
val extId = column[String]("extId") val extId = column[String]("extId")
@ -51,11 +51,11 @@ class MeetingDbTableDef(tag: Tag) extends Table[MeetingDbModel](tag, None, "meet
val presentationUploadExternalUrl = column[String]("presentationUploadExternalUrl") val presentationUploadExternalUrl = column[String]("presentationUploadExternalUrl")
val learningDashboardAccessToken = column[String]("learningDashboardAccessToken") val learningDashboardAccessToken = column[String]("learningDashboardAccessToken")
val createdTime = column[Long]("createdTime") val createdTime = column[Long]("createdTime")
val duration = column[Int]("duration") val durationInSeconds = column[Int]("durationInSeconds")
} }
object MeetingDAO { object MeetingDAO {
def insert(meetingProps: DefaultProps) = { def insert(meetingProps: DefaultProps, clientSettings: Map[String, Object]) = {
DatabaseConnection.db.run( DatabaseConnection.db.run(
TableQuery[MeetingDbTableDef].forceInsert( TableQuery[MeetingDbTableDef].forceInsert(
MeetingDbModel( MeetingDbModel(
@ -71,28 +71,46 @@ object MeetingDAO {
presentationUploadExternalUrl = meetingProps.meetingProp.presentationUploadExternalUrl, presentationUploadExternalUrl = meetingProps.meetingProp.presentationUploadExternalUrl,
learningDashboardAccessToken = meetingProps.password.learningDashboardAccessToken, learningDashboardAccessToken = meetingProps.password.learningDashboardAccessToken,
createdTime = meetingProps.durationProps.createdTime, createdTime = meetingProps.durationProps.createdTime,
duration = meetingProps.durationProps.duration durationInSeconds = meetingProps.durationProps.duration * 60
) )
) )
).onComplete { ).onComplete {
case Success(rowsAffected) => { case Success(rowsAffected) => {
DatabaseConnection.logger.debug(s"$rowsAffected row(s) inserted in Meeting table!") 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) MeetingUsersPoliciesDAO.insert(meetingProps.meetingProp.intId, meetingProps.usersProp)
MeetingLockSettingsDAO.insert(meetingProps.meetingProp.intId, meetingProps.lockSettingsProps) MeetingLockSettingsDAO.insert(meetingProps.meetingProp.intId, meetingProps.lockSettingsProps)
MeetingMetadataDAO.insert(meetingProps.meetingProp.intId, meetingProps.metadataProp) MeetingMetadataDAO.insert(meetingProps.meetingProp.intId, meetingProps.metadataProp)
MeetingRecordingPoliciesDAO.insert(meetingProps.meetingProp.intId, meetingProps.recordProp) MeetingRecordingPoliciesDAO.insert(meetingProps.meetingProp.intId, meetingProps.recordProp)
MeetingVoiceDAO.insert(meetingProps.meetingProp.intId, meetingProps.voiceProp) MeetingVoiceDAO.insert(meetingProps.meetingProp.intId, meetingProps.voiceProp)
ChatDAO.insert(meetingProps.meetingProp.intId, GroupChatApp.createDefaultPublicGroupChat())
MeetingWelcomeDAO.insert(meetingProps.meetingProp.intId, meetingProps.welcomeProp) MeetingWelcomeDAO.insert(meetingProps.meetingProp.intId, meetingProps.welcomeProp)
MeetingGroupDAO.insert(meetingProps.meetingProp.intId, meetingProps.groups) MeetingGroupDAO.insert(meetingProps.meetingProp.intId, meetingProps.groups)
MeetingBreakoutDAO.insert(meetingProps.meetingProp.intId, meetingProps.breakoutProps) MeetingBreakoutDAO.insert(meetingProps.meetingProp.intId, meetingProps.breakoutProps)
TimerDAO.insert(meetingProps.meetingProp.intId) TimerDAO.insert(meetingProps.meetingProp.intId)
LayoutDAO.insert(meetingProps.meetingProp.intId, meetingProps.usersProp.meetingLayout) LayoutDAO.insert(meetingProps.meetingProp.intId, meetingProps.usersProp.meetingLayout)
MeetingClientSettingsDAO.insert(meetingProps.meetingProp.intId, JsonUtils.mapToJson(clientSettings))
} }
case Failure(e) => DatabaseConnection.logger.error(s"Error inserting Meeting: $e") case 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) = { def delete(meetingId: String) = {
DatabaseConnection.db.run( DatabaseConnection.db.run(
TableQuery[MeetingDbTableDef] TableQuery[MeetingDbTableDef]

View File

@ -18,7 +18,8 @@ case class MeetingLockSettingsDbModel(
hideUserList: Boolean, hideUserList: Boolean,
lockOnJoin: Boolean, lockOnJoin: Boolean,
lockOnJoinConfigurable: Boolean, lockOnJoinConfigurable: Boolean,
hideViewersCursor: Boolean hideViewersCursor: Boolean,
hideViewersAnnotation: Boolean
) )
class MeetingLockSettingsDbTableDef(tag: Tag) extends Table[MeetingLockSettingsDbModel](tag, "meeting_lockSettings") { 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 lockOnJoin = column[Boolean]("lockOnJoin")
val lockOnJoinConfigurable = column[Boolean]("lockOnJoinConfigurable") val lockOnJoinConfigurable = column[Boolean]("lockOnJoinConfigurable")
val hideViewersCursor = column[Boolean]("hideViewersCursor") val hideViewersCursor = column[Boolean]("hideViewersCursor")
val hideViewersAnnotation = column[Boolean]("hideViewersAnnotation")
// def fk_meetingId: ForeignKeyQuery[MeetingDbTableDef, MeetingDbModel] = foreignKey("fk_meetingId", meetingId, TableQuery[MeetingDbTableDef])(_.meetingId) // 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 { object MeetingLockSettingsDAO {
@ -52,7 +54,8 @@ object MeetingLockSettingsDAO {
hideUserList = lockSettingsProps.hideUserList, hideUserList = lockSettingsProps.hideUserList,
lockOnJoin = lockSettingsProps.lockOnJoin, lockOnJoin = lockSettingsProps.lockOnJoin,
lockOnJoinConfigurable = lockSettingsProps.lockOnJoinConfigurable, lockOnJoinConfigurable = lockSettingsProps.lockOnJoinConfigurable,
hideViewersCursor = lockSettingsProps.hideViewersCursor hideViewersCursor = lockSettingsProps.hideViewersCursor,
hideViewersAnnotation = lockSettingsProps.hideViewersAnnotation,
) )
) )
).onComplete { ).onComplete {
@ -76,7 +79,8 @@ object MeetingLockSettingsDAO {
hideUserList = permissions.hideUserList, hideUserList = permissions.hideUserList,
lockOnJoin = permissions.lockOnJoin, lockOnJoin = permissions.lockOnJoin,
lockOnJoinConfigurable = permissions.lockOnJoinConfigurable, lockOnJoinConfigurable = permissions.lockOnJoinConfigurable,
hideViewersCursor = permissions.hideViewersCursor hideViewersCursor = permissions.hideViewersCursor,
hideViewersAnnotation = permissions.hideViewersAnnotation,
), ),
) )
).onComplete { ).onComplete {

View File

@ -73,8 +73,8 @@ object MeetingUsersPoliciesDAO {
.map(u => u.guestPolicy) .map(u => u.guestPolicy)
.update(policy.policy) .update(policy.policy)
).onComplete { ).onComplete {
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated on meeting_usersPolicies table!") 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 meeting_usersPolicies: $e") case Failure(e) => DatabaseConnection.logger.error(s"Error updating guestPolicy on meeting_usersPolicies: $e")
} }
} }
@ -90,8 +90,20 @@ object MeetingUsersPoliciesDAO {
} }
) )
).onComplete { ).onComplete {
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated on meeting_usersPolicies table!") 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 meeting_usersPolicies: $e") 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 package org.bigbluebutton.core.db
import com.github.tminglei.slickpg._ import com.github.tminglei.slickpg._
import spray.json.{ JsArray, JsBoolean, JsNumber, JsObject, JsString, JsValue, JsonWriter } import org.apache.pekko.http.scaladsl.model.ParsingException
import spray.json.{ _ } 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 trait PostgresProfile extends ExPostgresProfile
with PgArraySupport { with PgArraySupport
// def pgjson = "jsonb" // jsonb support is in postgres 9.4.0 onward; for 9.3.x use "json" 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+ // Add back `capabilities.insertOrUpdate` to enable native `upsert` support; for postgres 9.5+
override protected def computeCapabilities: Set[slick.basic.Capability] = override protected def computeCapabilities: Set[slick.basic.Capability] =
@ -14,7 +18,7 @@ trait PostgresProfile extends ExPostgresProfile
override val api = PgAPI 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) 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 m: Map[_, _] => JsObject(m.asInstanceOf[Map[String, Any]].map { case (k, v) => k -> write(v) })
case l: List[_] => JsArray(l.map(write).toVector) case l: List[_] => JsArray(l.map(write).toVector)
case a: Array[_] => JsArray(a.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 _ => throw new IllegalArgumentException(s"Unsupported type: ${x.getClass.getName}")
// case _ => JsNull // case _ => JsNull
} }
@ -46,7 +52,16 @@ object JsonUtils {
} }
def mapToJson(genericMap: Map[String, Any]) = { 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, annotationId = annotation.id,
pageId = annotation.wbId, pageId = annotation.wbId,
userId = annotation.userId, userId = annotation.userId,
annotationInfo = JsonUtils.mapToJson(annotation.annotationInfo), annotationInfo = JsonUtils.mapToJson(annotation.annotationInfo).compactPrint,
lastHistorySequence = sequence.getOrElse(0), lastHistorySequence = sequence.getOrElse(0),
lastUpdatedAt = new java.sql.Timestamp(System.currentTimeMillis()) lastUpdatedAt = new java.sql.Timestamp(System.currentTimeMillis())
) )

View File

@ -34,7 +34,7 @@ object PresAnnotationHistoryDAO {
annotationId = annotationDiff.id, annotationId = annotationDiff.id,
pageId = annotationDiff.wbId, pageId = annotationDiff.wbId,
userId = annotationDiff.userId, 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 package org.bigbluebutton.core.db
import org.bigbluebutton.core.models.{ PresentationInPod, PresentationPage } import org.bigbluebutton.core.models.{ PresentationInPod, PresentationPage }
import PostgresProfile.api._
import org.bigbluebutton.core.models.PresentationInPod import spray.json.JsValue
import slick.jdbc.PostgresProfile.api._ import spray.json._
import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{ Failure, Success } import scala.util.{ Failure, Success }
@ -12,7 +12,8 @@ case class PresPageDbModel(
pageId: String, pageId: String,
presentationId: String, presentationId: String,
num: Int, num: Int,
urls: String, urlsJson: JsValue,
content: String,
slideRevealed: Boolean, slideRevealed: Boolean,
current: Boolean, current: Boolean,
xOffset: Double, xOffset: Double,
@ -25,14 +26,15 @@ case class PresPageDbModel(
viewBoxHeight: Double, viewBoxHeight: Double,
maxImageWidth: Int, maxImageWidth: Int,
maxImageHeight: Int, maxImageHeight: Int,
converted: Boolean uploadCompleted: Boolean
) )
class PresPageDbTableDef(tag: Tag) extends Table[PresPageDbModel](tag, None, "pres_page") { class PresPageDbTableDef(tag: Tag) extends Table[PresPageDbModel](tag, None, "pres_page") {
val pageId = column[String]("pageId", O.PrimaryKey) val pageId = column[String]("pageId", O.PrimaryKey)
val presentationId = column[String]("presentationId") val presentationId = column[String]("presentationId")
val num = column[Int]("num") 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 slideRevealed = column[Boolean]("slideRevealed")
val current = column[Boolean]("current") val current = column[Boolean]("current")
val xOffset = column[Double]("xOffset") 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 viewBoxHeight = column[Double]("viewBoxHeight")
val maxImageWidth = column[Int]("maxImageWidth") val maxImageWidth = column[Int]("maxImageWidth")
val maxImageHeight = column[Int]("maxImageHeight") 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) // 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 { 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) = { def setCurrentPage(presentation: PresentationInPod, pageId: String) = {
DatabaseConnection.db.run( DatabaseConnection.db.run(

View File

@ -1,27 +1,70 @@
package org.bigbluebutton.core.db package org.bigbluebutton.core.db
import slick.jdbc.PostgresProfile.api._ import PostgresProfile.api._
import org.bigbluebutton.core.models.{ PresentationInPod } import org.bigbluebutton.core.models.PresentationInPod
import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{Failure, Success} import scala.util.{Failure, Success}
import spray.json._ 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") { class PresPresentationDbTableDef(tag: Tag) extends Table[PresPresentationDbModel](tag, None, "pres_presentation") {
val presentationId = column[String]("presentationId", O.PrimaryKey) val presentationId = column[String]("presentationId", O.PrimaryKey)
val meetingId = column[String]("meetingId") 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 current = column[Boolean]("current")
val downloadable = column[Boolean]("downloadable") val downloadable = column[Boolean]("downloadable")
val downloadFileExtension = column[Option[String]]("downloadFileExtension")
val downloadFileUri = column[Option[String]]("downloadFileUri")
val removable = column[Boolean]("removable") val removable = column[Boolean]("removable")
val uploadInProgress = column[Boolean]("uploadInProgress")
val uploadCompleted = column[Boolean]("uploadCompleted") val uploadCompleted = column[Boolean]("uploadCompleted")
val numPages = column[Int]("numPages") val uploadErrorMsgKey = column[Option[String]]("uploadErrorMsgKey")
val errorMsgKey = column[String]("errorMsgKey") val uploadErrorDetailsJson = column[Option[JsValue]]("uploadErrorDetailsJson")
val errorDetails = column[String]("errorDetails") 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) // 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 { object PresPresentationDAO {
@ -31,24 +74,107 @@ object PresPresentationDAO {
} }
} }
def insertOrUpdate(meetingId: String, presentation: PresentationInPod) = { def insertToken(meetingId: String, userId: String, temporaryId: String, presentationId: String, uploadToken: String, filename: String) = {
DatabaseConnection.db.run( val dbRun = DatabaseConnection.db.run(
TableQuery[PresPresentationDbTableDef].insertOrUpdate( TableQuery[PresPresentationDbTableDef].forceInsert(
PresPresentationDbModel( PresPresentationDbModel(
presentationId = presentation.id, presentationId = presentationId,
meetingId = meetingId, meetingId = meetingId,
uploadUserId = Some(userId),
uploadTemporaryId = Some(temporaryId),
uploadToken = Some(uploadToken),
name = filename,
filenameConverted = "",
isDefault = false,
current = false, //Set after pages were inserted current = false, //Set after pages were inserted
downloadable = presentation.downloadable, downloadable = false,
removable = presentation.removable, downloadFileExtension = None,
uploadCompleted = presentation.uploadCompleted, downloadFileUri = None,
numPages = presentation.numPages, removable = false,
errorMsgKey = presentation.errorMsgKey, uploadInProgress = false,
errorDetails = presentation.errorDetails.toJson.asJsObject.compactPrint 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 { ).onComplete {
case Success(rowsAffected) => { 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( DatabaseConnection.db.run(DBIO.sequence(
for { for {
@ -59,7 +185,8 @@ object PresPresentationDAO {
pageId = page._2.id, pageId = page._2.id,
presentationId = presentation.id, presentationId = presentation.id,
num = page._2.num, num = page._2.num,
urls = page._2.urls.toJson.asJsObject.compactPrint, urlsJson = page._2.urls.toJson,
content = page._2.content,
slideRevealed = page._2.current, slideRevealed = page._2.current,
current = page._2.current, current = page._2.current,
xOffset = page._2.xOffset, xOffset = page._2.xOffset,
@ -72,22 +199,23 @@ object PresPresentationDAO {
viewBoxHeight = 1, viewBoxHeight = 1,
maxImageWidth = 1440, maxImageWidth = 1440,
maxImageHeight = 1080, maxImageHeight = 1080,
converted = page._2.converted uploadCompleted = page._2.converted
) )
) )
} }
).transactionally) ).transactionally)
.onComplete { .onComplete {
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) inserted on PresentationPage table!") case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated on PresentationPage table!")
case Failure(e) => DatabaseConnection.logger.debug(s"Error inserting PresentationPage: $e") case Failure(e) => DatabaseConnection.logger.debug(s"Error updating PresentationPage: $e")
} }
//Set current //Set current
if (presentation.current) { if (presentation.current) {
setCurrentPres(presentation.id) 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( DatabaseConnection.db.run(
sqlu"""UPDATE pres_presentation SET sqlu"""UPDATE pres_presentation SET
"current" = (case when "presentationId" = ${presentationId} then true else false end) "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 { ).onComplete {
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated current on PresPresentation table") 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") 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]) = { def updateErrors(presentationId: String, errorMsgKey: String, errorDetails: scala.collection.immutable.Map[String, String]) = {
DatabaseConnection.db.run( DatabaseConnection.db.run(
TableQuery[PresPresentationDbTableDef] TableQuery[PresPresentationDbTableDef]
.filter(_.presentationId === presentationId) .filter(_.presentationId === presentationId)
.map(p => (p.errorMsgKey, p.errorDetails)) .map(p => (p.uploadErrorMsgKey, p.uploadErrorDetailsJson))
.update(errorMsgKey, errorDetails.toJson.asJsObject.compactPrint) .update(Some(errorMsgKey), Some(errorDetails.toJson))
).onComplete { ).onComplete {
case Success(rowAffected) => DatabaseConnection.logger.debug(s"$rowAffected row(s) updated errorMsgKey on PresPresentation table") 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") 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 package org.bigbluebutton.core.db
import org.bigbluebutton.core.apps.TimerModel 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 slick.jdbc.PostgresProfile.api._
import scala.util.{Failure, Success} import scala.util.{Failure, Success}
@ -59,7 +59,7 @@ object TimerDAO {
TableQuery[TimerDbTableDef] TableQuery[TimerDbTableDef]
.filter(_.meetingId === meetingId) .filter(_.meetingId === meetingId)
.map(t => (t.stopwatch, t.running, t.active, t.time, t.accumulated, t.startedAt, t.endedAt, t.songTrack)) .map(t => (t.stopwatch, t.running, t.active, t.time, t.accumulated, t.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 { ).onComplete {
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated on Timer table!") 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") val connectionAliveAt = column[Option[java.sql.Timestamp]]("connectionAliveAt")
} }
object UserConnectionStatusdDAO { object UserConnectionStatusDAO {
def insert(meetingId: String, userId: String) = { def insert(meetingId: String, userId: String) = {
DatabaseConnection.db.run( DatabaseConnection.db.run(

View File

@ -1,5 +1,5 @@
package org.bigbluebutton.core.db package org.bigbluebutton.core.db
import org.bigbluebutton.core.models.{RegisteredUser} import org.bigbluebutton.core.models.{RegisteredUser, VoiceUserState}
import slick.jdbc.PostgresProfile.api._ import slick.jdbc.PostgresProfile.api._
import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.ExecutionContext.Implicits.global
@ -13,21 +13,25 @@ case class UserDbModel(
role: String, role: String,
avatar: String = "", avatar: String = "",
color: String = "", color: String = "",
sessionToken: String = "",
authed: Boolean = false, authed: Boolean = false,
joined: Boolean = false, joined: Boolean = false,
joinErrorMessage: Option[String],
joinErrorCode: Option[String],
banned: Boolean = false, banned: Boolean = false,
loggedOut: Boolean = false, loggedOut: Boolean = false,
guest: Boolean, guest: Boolean,
guestStatus: String, guestStatus: String,
registeredOn: Long, registeredOn: Long,
excludeFromDashboard: Boolean, excludeFromDashboard: Boolean,
enforceLayout: Option[String],
) )
class UserDbTableDef(tag: Tag) extends Table[UserDbModel](tag, None, "user") { class UserDbTableDef(tag: Tag) extends Table[UserDbModel](tag, None, "user") {
override def * = ( override def * = (
userId,extId,meetingId,name,role,avatar,color,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 userId = column[String]("userId", O.PrimaryKey)
val extId = column[String]("extId") val extId = column[String]("extId")
val meetingId = column[String]("meetingId") val meetingId = column[String]("meetingId")
@ -35,14 +39,18 @@ class UserDbTableDef(tag: Tag) extends Table[UserDbModel](tag, None, "user") {
val role = column[String]("role") val role = column[String]("role")
val avatar = column[String]("avatar") val avatar = column[String]("avatar")
val color = column[String]("color") val color = column[String]("color")
val sessionToken = column[String]("sessionToken")
val authed = column[Boolean]("authed") val authed = column[Boolean]("authed")
val joined = column[Boolean]("joined") val joined = column[Boolean]("joined")
val joinErrorCode = column[Option[String]]("joinErrorCode")
val joinErrorMessage = column[Option[String]]("joinErrorMessage")
val banned = column[Boolean]("banned") val banned = column[Boolean]("banned")
val loggedOut = column[Boolean]("loggedOut") val loggedOut = column[Boolean]("loggedOut")
val guest = column[Boolean]("guest") val guest = column[Boolean]("guest")
val guestStatus = column[String]("guestStatus") val guestStatus = column[String]("guestStatus")
val registeredOn = column[Long]("registeredOn") val registeredOn = column[Long]("registeredOn")
val excludeFromDashboard = column[Boolean]("excludeFromDashboard") val excludeFromDashboard = column[Boolean]("excludeFromDashboard")
val enforceLayout = column[Option[String]]("enforceLayout")
} }
object UserDAO { object UserDAO {
@ -57,23 +65,30 @@ object UserDAO {
role = regUser.role, role = regUser.role,
avatar = regUser.avatarURL, avatar = regUser.avatarURL,
color = regUser.color, color = regUser.color,
sessionToken = regUser.sessionToken,
authed = regUser.authed, authed = regUser.authed,
joined = regUser.joined, joined = regUser.joined,
joinErrorCode = None,
joinErrorMessage = None,
banned = regUser.banned, banned = regUser.banned,
loggedOut = regUser.loggedOut, loggedOut = regUser.loggedOut,
guest = regUser.guest, guest = regUser.guest,
guestStatus = regUser.guestStatus, guestStatus = regUser.guestStatus,
registeredOn = regUser.registeredOn, registeredOn = regUser.registeredOn,
excludeFromDashboard = regUser.excludeFromDashboard excludeFromDashboard = regUser.excludeFromDashboard,
enforceLayout = regUser.enforceLayout match {
case "" => None
case enforceLayout: String => Some(enforceLayout)
}
) )
) )
).onComplete { ).onComplete {
case Success(rowsAffected) => { case Success(rowsAffected) => {
DatabaseConnection.logger.debug(s"$rowsAffected row(s) inserted in User table!") DatabaseConnection.logger.debug(s"$rowsAffected row(s) inserted in User table!")
ChatUserDAO.insertUserPublicChat(meetingId, regUser.id) UserConnectionStatusDAO.insert(meetingId, regUser.id)
UserConnectionStatusdDAO.insert(meetingId, regUser.id)
UserCustomParameterDAO.insert(regUser.id, regUser.customParameters) 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") 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) = { 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( DatabaseConnection.db.run(
TableQuery[UserDbTableDef] TableQuery[UserDbTableDef]
.filter(_.userId === intId) .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( case class UserReactionDbModel(
userId: String, userId: String,
reactionEmoji: String, reactionEmoji: String,
duration: Int, durationInSeconds: Int,
createdAt: java.sql.Timestamp createdAt: java.sql.Timestamp
) )
class UserReactionDbTableDef(tag: Tag) extends Table[UserReactionDbModel](tag, "user_reaction") { class UserReactionDbTableDef(tag: Tag) extends Table[UserReactionDbModel](tag, "user_reaction") {
val userId = column[String]("userId") val userId = column[String]("userId")
val reactionEmoji = column[String]("reactionEmoji") val reactionEmoji = column[String]("reactionEmoji")
val duration = column[Int]("duration") val durationInSeconds = column[Int]("durationInSeconds")
val createdAt = column[java.sql.Timestamp]("createdAt") 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 { object UserReactionDAO {
@ -29,7 +29,7 @@ object UserReactionDAO {
UserReactionDbModel( UserReactionDbModel(
userId = userId, userId = userId,
reactionEmoji = reactionEmoji, reactionEmoji = reactionEmoji,
duration = 60, durationInSeconds = 60,
createdAt = new java.sql.Timestamp(System.currentTimeMillis()) 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, talking: Boolean,
floor: Boolean, floor: Boolean,
lastFloorTime: String, lastFloorTime: String,
voiceConf: String,
startTime: Option[Long], startTime: Option[Long],
endTime: Option[Long], endTime: Option[Long],
) )
@ -27,7 +26,7 @@ case class UserVoiceDbModel(
class UserVoiceDbTableDef(tag: Tag) extends Table[UserVoiceDbModel](tag, None, "user_voice") { class UserVoiceDbTableDef(tag: Tag) extends Table[UserVoiceDbModel](tag, None, "user_voice") {
override def * = ( override def * = (
userId, voiceUserId, callerName, callerNum, callingWith, joined, listenOnly, 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) ) <> (UserVoiceDbModel.tupled, UserVoiceDbModel.unapply)
val userId = column[String]("userId", O.PrimaryKey) val userId = column[String]("userId", O.PrimaryKey)
val voiceUserId = column[String]("voiceUserId") val voiceUserId = column[String]("voiceUserId")
@ -42,6 +41,9 @@ class UserVoiceDbTableDef(tag: Tag) extends Table[UserVoiceDbModel](tag, None, "
val floor = column[Boolean]("floor") val floor = column[Boolean]("floor")
val lastFloorTime = column[String]("lastFloorTime") val lastFloorTime = column[String]("lastFloorTime")
val voiceConf = column[String]("voiceConf") 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 startTime = column[Option[Long]]("startTime")
val endTime = column[Option[Long]]("endTime") val endTime = column[Option[Long]]("endTime")
} }
@ -64,7 +66,6 @@ object UserVoiceDAO {
talking = voiceUserState.talking, talking = voiceUserState.talking,
floor = voiceUserState.floor, floor = voiceUserState.floor,
lastFloorTime = voiceUserState.lastFloorTime, lastFloorTime = voiceUserState.lastFloorTime,
voiceConf = "",
startTime = None, startTime = None,
endTime = None endTime = None
) )
@ -85,7 +86,7 @@ object UserVoiceDAO {
.update((voiceUserState.listenOnly, voiceUserState.muted, voiceUserState.floor, voiceUserState.lastFloorTime)) .update((voiceUserState.listenOnly, voiceUserState.muted, voiceUserState.floor, voiceUserState.lastFloorTime))
).onComplete { ).onComplete {
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated on user_voice table!") 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 //Meteor sets this props instead of removing
// muted: false // muted: false
// talking: false // talking: false
@ -124,10 +137,11 @@ object UserVoiceDAO {
DatabaseConnection.db.run( DatabaseConnection.db.run(
TableQuery[UserVoiceDbTableDef] TableQuery[UserVoiceDbTableDef]
.filter(_.userId === userId) .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 { ).onComplete {
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"Voice of user ${userId} deleted") case Success(rowsAffected) => DatabaseConnection.logger.debug(s"Voice of user ${userId} deleted (joined=false)")
case Failure(e) => DatabaseConnection.logger.error(s"Error deleting voice: $e") case Failure(e) => DatabaseConnection.logger.error(s"Error deleting voice user: $e")
} }
} }

View File

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

View File

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

View File

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

View File

@ -346,8 +346,6 @@ class ReceivedJsonMsgHandlerActor(
routeGenericMsg[CreateNewPresentationPodPubMsg](envelope, jsonNode) routeGenericMsg[CreateNewPresentationPodPubMsg](envelope, jsonNode)
case RemovePresentationPodPubMsg.NAME => case RemovePresentationPodPubMsg.NAME =>
routeGenericMsg[RemovePresentationPodPubMsg](envelope, jsonNode) routeGenericMsg[RemovePresentationPodPubMsg](envelope, jsonNode)
case SetPresenterInPodReqMsg.NAME =>
routeGenericMsg[SetPresenterInPodReqMsg](envelope, jsonNode)
// Caption // Caption
case EditCaptionHistoryPubMsg.NAME => case EditCaptionHistoryPubMsg.NAME =>
@ -417,6 +415,10 @@ class ReceivedJsonMsgHandlerActor(
case CreateGroupChatReqMsg.NAME => case CreateGroupChatReqMsg.NAME =>
routeGenericMsg[CreateGroupChatReqMsg](envelope, jsonNode) routeGenericMsg[CreateGroupChatReqMsg](envelope, jsonNode)
//Plugin
case DispatchPluginDataChannelMessageMsg.NAME =>
routeGenericMsg[DispatchPluginDataChannelMessageMsg](envelope, jsonNode)
// ExternalVideo // ExternalVideo
case StartExternalVideoPubMsg.NAME => case StartExternalVideoPubMsg.NAME =>
routeGenericMsg[StartExternalVideoPubMsg](envelope, jsonNode) routeGenericMsg[StartExternalVideoPubMsg](envelope, jsonNode)
@ -447,6 +449,13 @@ class ReceivedJsonMsgHandlerActor(
case TimerEndedPubMsg.NAME => case TimerEndedPubMsg.NAME =>
routeGenericMsg[TimerEndedPubMsg](envelope, jsonNode) 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 _ => case _ =>
log.error("Cannot route envelope name " + envelope.name) log.error("Cannot route envelope name " + envelope.name)
// do nothing // do nothing

View File

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

View File

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

View File

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

View File

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

View File

@ -56,7 +56,7 @@ object FakeUserGenerator {
val color = "#ff6242" val color = "#ff6242"
val ru = RegisteredUsers.create(userId = id, extId, name, role, 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) RegisteredUsers.add(users, ru, meetingId)
ru 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) user <- findUserByIntId(meeting, msg.body.userId)
} yield { } yield {
if (msg.body.reactionEmoji != "none") { 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 updatedUser = user.copy(reactions = user.reactions :+ Emoji(msg.body.reactionEmoji))
val updatedMeeting = meeting.copy(users = meeting.users + (updatedUser.userKey -> updatedUser)) val updatedMeeting = meeting.copy(users = meeting.users + (updatedUser.userKey -> updatedUser))
meetings += (updatedMeeting.intId -> updatedMeeting) 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 sessionToken = RandomStringGenerator.randomAlphanumericString(16)
val avatarURL = "https://www." + RandomStringGenerator.randomAlphanumericString(32) + ".com/" + val avatarURL = "https://www." + RandomStringGenerator.randomAlphanumericString(32) + ".com/" +
RandomStringGenerator.randomAlphanumericString(10) + ".png" RandomStringGenerator.randomAlphanumericString(10) + ".png"
val color = "#ff6242"
val ru = RegisteredUsers.create(userId = id, extId, name, role, 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") RegisteredUsers.add(users, ru, meetingId = "test")
ru 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 { redis {
host="127.0.0.1" host="127.0.0.1"
port=6379 port=6379

View File

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

View File

@ -89,7 +89,8 @@ case class DefaultProps(
metadataProp: MetadataProp, metadataProp: MetadataProp,
lockSettingsProps: LockSettingsProps, lockSettingsProps: LockSettingsProps,
systemProps: SystemProps, systemProps: SystemProps,
groups: Vector[GroupProps] groups: Vector[GroupProps],
overrideClientSettings: String
) )
case class StartEndTimeStatus(startTime: Long, endTime: Long) 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, case class PresentationVO(id: String, temporaryPresentationId: String, name: String, current: Boolean = false,
pages: Vector[PageVO], downloadable: Boolean, removable: Boolean, pages: Vector[PageVO], downloadable: Boolean, removable: Boolean,
isInitialPresentation: Boolean, filenameConverted: String) defaultPresentation: Boolean, filenameConverted: String)
case class PageVO(id: String, num: Int, thumbUri: String = "", case class PageVO(id: String, num: Int, thumbUri: String = "",
txtUri: String, svgUri: String, current: Boolean = false, xOffset: Double = 0, txtUri: String, svgUri: String, current: Boolean = false, xOffset: Double = 0,
@ -15,6 +15,7 @@ case class PresentationPageConvertedVO(
id: String, id: String,
num: Int, num: Int,
urls: Map[String, String], urls: Map[String, String],
content: String,
current: Boolean = false, current: Boolean = false,
width: Double = 1440D, width: Double = 1440D,
height: Double = 1080D height: Double = 1080D

View File

@ -11,6 +11,7 @@ object GroupChatMessageType {
val POLL = "poll" val POLL = "poll"
val BREAKOUTROOM_MOD_MSG = "breakoutRoomModeratorMsg" val BREAKOUTROOM_MOD_MSG = "breakoutRoomModeratorMsg"
val PUBLIC_CHAT_HIST_CLEARED = "publicChatHistoryCleared" val PUBLIC_CHAT_HIST_CLEARED = "publicChatHistoryCleared"
val USER_AWAY_STATUS_MSG = "userAwayStatusMsg"
} }
case class GroupChatUser(id: String, name: String = "", role: String = "VIEWER") 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" } object PresentationUploadTokenReqMsg { val NAME = "PresentationUploadTokenReqMsg" }
case class PresentationUploadTokenReqMsg(header: BbbClientMsgHeader, body: PresentationUploadTokenReqMsgBody) extends StandardMsg 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" } object GetAllPresentationPodsReqMsg { val NAME = "GetAllPresentationPodsReqMsg" }
case class GetAllPresentationPodsReqMsg(header: BbbClientMsgHeader, body: GetAllPresentationPodsReqMsgBody) extends StandardMsg 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 SetCurrentPagePubMsg(header: BbbClientMsgHeader, body: SetCurrentPagePubMsgBody) extends StandardMsg
case class SetCurrentPagePubMsgBody(podId: String, presentationId: String, pageId: String) 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" } object RemovePresentationPubMsg { val NAME = "RemovePresentationPubMsg" }
case class RemovePresentationPubMsg(header: BbbClientMsgHeader, body: RemovePresentationPubMsgBody) extends StandardMsg case class RemovePresentationPubMsg(header: BbbClientMsgHeader, body: RemovePresentationPubMsgBody) extends StandardMsg
case class RemovePresentationPubMsgBody(podId: String, presentationId: String) case class RemovePresentationPubMsgBody(podId: String, presentationId: String)
@ -139,7 +135,9 @@ case class PresentationPageConversionStartedSysMsgBody(
podId: String, podId: String,
presentationId: String, presentationId: String,
current: Boolean, current: Boolean,
default: Boolean,
presName: String, presName: String,
presFilenameConverted: String,
downloadable: Boolean, downloadable: Boolean,
removable: Boolean, removable: Boolean,
authzToken: String, authzToken: String,
@ -221,7 +219,7 @@ case class PdfConversionInvalidErrorEvtMsgBody(podId: String, messageKey: String
object PresentationUploadTokenPassRespMsg { val NAME = "PresentationUploadTokenPassRespMsg" } object PresentationUploadTokenPassRespMsg { val NAME = "PresentationUploadTokenPassRespMsg" }
case class PresentationUploadTokenPassRespMsg(header: BbbClientMsgHeader, body: PresentationUploadTokenPassRespMsgBody) extends StandardMsg 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" } object PresentationUploadTokenFailRespMsg { val NAME = "PresentationUploadTokenFailRespMsg" }
case class PresentationUploadTokenFailRespMsg(header: BbbClientMsgHeader, body: PresentationUploadTokenFailRespMsgBody) extends StandardMsg 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 ------------ // ------------ akka-apps to bbb-common-web ------------
object PresentationUploadTokenSysPubMsg { val NAME = "PresentationUploadTokenSysPubMsg" } object PresentationUploadTokenSysPubMsg { val NAME = "PresentationUploadTokenSysPubMsg" }
case class PresentationUploadTokenSysPubMsg(header: BbbClientMsgHeader, body: PresentationUploadTokenSysPubMsgBody) extends BbbCoreMsg 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 ------------ // ------------ akka-apps to bbb-common-web ------------

View File

@ -242,6 +242,31 @@ case class InvalidateUserGraphqlConnectionSysMsg(
) extends BbbCoreMsg ) extends BbbCoreMsg
case class InvalidateUserGraphqlConnectionSysMsgBody(meetingId: String, userId: String, sessionToken: String, reason: String) 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 * 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, case class RegisterUserReqMsgBody(meetingId: String, intUserId: String, name: String, role: String,
extUserId: String, authToken: String, sessionToken: String, avatarURL: String, extUserId: String, authToken: String, sessionToken: String, avatarURL: String,
guest: Boolean, authed: Boolean, guestStatus: String, excludeFromDashboard: Boolean, guest: Boolean, authed: Boolean, guestStatus: String, excludeFromDashboard: Boolean,
customParameters: Map[String, String]) enforceLayout: String, customParameters: Map[String, String])
object UserRegisteredRespMsg { val NAME = "UserRegisteredRespMsg" } object UserRegisteredRespMsg { val NAME = "UserRegisteredRespMsg" }
case class UserRegisteredRespMsg( case class UserRegisteredRespMsg(
@ -302,7 +302,7 @@ case class UserMobileFlagChangedEvtMsgBody(userId: String, mobile: Boolean)
object AssignPresenterReqMsg { val NAME = "AssignPresenterReqMsg" } object AssignPresenterReqMsg { val NAME = "AssignPresenterReqMsg" }
case class AssignPresenterReqMsg(header: BbbClientMsgHeader, body: AssignPresenterReqMsgBody) extends StandardMsg 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. * 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, public void registerUser(String meetingID, String internalUserId,
String fullname, String role, String externUserID, String fullname, String role, String externUserID,
String authToken, String sessionToken, String avatarURL, Boolean guest, String authToken, String sessionToken, String avatarURL, Boolean guest,
Boolean authed, String guestStatus, Boolean excludeFromDashboard, Boolean leftGuestLobby, Map<String, String> customParameters) { Boolean authed, String guestStatus, Boolean excludeFromDashboard, Boolean leftGuestLobby,
handle(new RegisterUser(meetingID, internalUserId, fullname, role, String enforceLayout, Map<String, String> customParameters) {
externUserID, authToken, sessionToken, avatarURL, guest, authed, guestStatus, excludeFromDashboard, leftGuestLobby, customParameters)); handle(
new RegisterUser(meetingID, internalUserId, fullname, role,
externUserID, authToken, sessionToken, avatarURL, guest, authed, guestStatus,
excludeFromDashboard, leftGuestLobby, enforceLayout, customParameters
)
);
Meeting m = getMeeting(meetingID); Meeting m = getMeeting(meetingID);
if (m != null) { 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); m.userRegistered(ruser);
} }
} }
@ -407,7 +413,8 @@ public class MeetingService implements MessageListener {
m.getMuteOnStart(), m.getAllowModsToUnmuteUsers(), m.getAllowModsToEjectCameras(), m.getMeetingKeepEvents(), m.getMuteOnStart(), m.getAllowModsToUnmuteUsers(), m.getAllowModsToEjectCameras(), m.getMeetingKeepEvents(),
m.breakoutRoomsParams, m.lockSettingsParams, m.getHtml5InstanceId(), m.breakoutRoomsParams, m.lockSettingsParams, m.getHtml5InstanceId(),
m.getGroups(), m.getDisabledFeatures(), m.getNotifyRecordingIsOn(), m.getGroups(), m.getDisabledFeatures(), m.getNotifyRecordingIsOn(),
m.getPresentationUploadExternalDescription(), m.getPresentationUploadExternalUrl()); m.getPresentationUploadExternalDescription(), m.getPresentationUploadExternalUrl(),
m.getOverrideClientSettings());
} }
private String formatPrettyDate(Long timestamp) { private String formatPrettyDate(Long timestamp) {
@ -422,7 +429,7 @@ public class MeetingService implements MessageListener {
gw.registerUser(message.meetingID, gw.registerUser(message.meetingID,
message.internalUserId, message.fullname, message.role, message.internalUserId, message.fullname, message.role,
message.externUserID, message.authToken, message.sessionToken, message.avatarURL, message.guest, 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) { public Meeting getMeeting(String meetingId) {
@ -733,7 +740,7 @@ public class MeetingService implements MessageListener {
uploadAuthzTokens.put(message.authzToken, message); uploadAuthzTokens.put(message.authzToken, message);
} }
private void expirePresentationUploadToken(String usedToken) { public void expirePresentationUploadToken(String usedToken) {
uploadAuthzTokens.remove(usedToken); uploadAuthzTokens.remove(usedToken);
} }

View File

@ -19,13 +19,10 @@
package org.bigbluebutton.api; package org.bigbluebutton.api;
import java.io.IOException;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.net.URLDecoder; import java.net.URLDecoder;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.*; import java.util.*;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; 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.codec.digest.DigestUtils;
import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils; 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.BreakoutRoomsParams;
import org.bigbluebutton.api.domain.LockSettingsParams; import org.bigbluebutton.api.domain.LockSettingsParams;
import org.bigbluebutton.api.domain.Meeting; import org.bigbluebutton.api.domain.Meeting;
@ -145,6 +136,7 @@ public class ParamsProcessorUtil {
private String bbbVersion = ""; private String bbbVersion = "";
private Boolean allowRevealOfBBBVersion = false; private Boolean allowRevealOfBBBVersion = false;
private Boolean allowOverrideClientSettingsOnCreateCall = false;
private String formatConfNum(String s) { private String formatConfNum(String s) {
if (s.length() > 5) { if (s.length() > 5) {
@ -912,6 +904,10 @@ public class ParamsProcessorUtil {
return allowRevealOfBBBVersion; return allowRevealOfBBBVersion;
} }
public Boolean getAllowOverrideClientSettingsOnCreateCall() {
return allowOverrideClientSettingsOnCreateCall;
}
public String processWelcomeMessage(String message, Boolean isBreakout) { public String processWelcomeMessage(String message, Boolean isBreakout) {
String welcomeMessage = message; String welcomeMessage = message;
if (StringUtils.isEmpty(message)) { if (StringUtils.isEmpty(message)) {
@ -1156,6 +1152,11 @@ public class ParamsProcessorUtil {
return true; return true;
} }
public boolean parentMeetingExists(String parentMeetingId) {
Meeting meeting = ServiceUtils.findMeetingFromMeetingID(parentMeetingId);
return meeting != null;
}
/************************************************* /*************************************************
* Setters * Setters
************************************************/ ************************************************/
@ -1515,4 +1516,8 @@ public class ParamsProcessorUtil {
this.allowRevealOfBBBVersion = allowVersion; 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.File;
import java.io.IOException; import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.UUID; import java.util.UUID;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
public final class Util { public final class Util {
@ -20,6 +23,14 @@ public final class Util {
throw new IllegalStateException("Utility class"); 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) { public static boolean isMeetingIdValidFormat(String id) {
Matcher matcher = MEETING_ID_PATTERN.matcher(id); Matcher matcher = MEETING_ID_PATTERN.matcher(id);
if (matcher.matches()) { if (matcher.matches()) {
@ -51,7 +62,11 @@ public final class Util {
} }
public static String createNewFilename(String presId, String fileExt) { public static String createNewFilename(String presId, String fileExt) {
if (!fileExt.isEmpty()) {
return presId + "." + fileExt; return presId + "." + fileExt;
} else {
return presId;
}
} }
public static File createPresentationDir(String meetingId, String presentationDir, String presentationId) { public static File createPresentationDir(String meetingId, String presentationDir, String presentationId) {

View File

@ -118,6 +118,8 @@ public class Meeting {
private Integer html5InstanceId; private Integer html5InstanceId;
private String overrideClientSettings = "";
public Meeting(Meeting.Builder builder) { public Meeting(Meeting.Builder builder) {
name = builder.name; name = builder.name;
extMeetingId = builder.externalId; extMeetingId = builder.externalId;
@ -1131,4 +1133,12 @@ public class Meeting {
return new Meeting(this); 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 Boolean excludeFromDashboard;
private Long guestWaitedOn; private Long guestWaitedOn;
private Boolean leftGuestLobby; 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.authToken = authToken;
this.userId = userId; this.userId = userId;
this.guestStatus = guestStatus; this.guestStatus = guestStatus;
this.excludeFromDashboard = excludeFromDashboard; this.excludeFromDashboard = excludeFromDashboard;
this.leftGuestLobby = leftGuestLobby; this.leftGuestLobby = leftGuestLobby;
this.enforceLayout = enforceLayout;
Long currentTimeMillis = System.currentTimeMillis(); Long currentTimeMillis = System.currentTimeMillis();
this.registeredOn = currentTimeMillis; this.registeredOn = currentTimeMillis;
@ -30,6 +33,10 @@ public class RegisteredUser {
return guestStatus; return guestStatus;
} }
public void setLeftGuestLobby(boolean bool) {
this.leftGuestLobby = bool;
}
public Boolean getLeftGuestLobby() { public Boolean getLeftGuestLobby() {
return leftGuestLobby; return leftGuestLobby;
} }
@ -38,6 +45,14 @@ public class RegisteredUser {
this.excludeFromDashboard = excludeFromDashboard; this.excludeFromDashboard = excludeFromDashboard;
} }
public String getEnforceLayout() {
return enforceLayout;
}
public void setEnforceLayout(String enforceLayout) {
this.enforceLayout = enforceLayout;
}
public Boolean getExcludeFromDashboard() { public Boolean getExcludeFromDashboard() {
return excludeFromDashboard; return excludeFromDashboard;
} }
@ -46,9 +61,6 @@ public class RegisteredUser {
this.guestWaitedOn = System.currentTimeMillis(); this.guestWaitedOn = System.currentTimeMillis();
} }
public void setLeftGuestLobby(boolean bool) {
this.leftGuestLobby = bool;
}
public Long getGuestWaitedOn() { public Long getGuestWaitedOn() {
return this.guestWaitedOn; return this.guestWaitedOn;
} }

View File

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

View File

@ -2,13 +2,15 @@ package org.bigbluebutton.api.messaging.messages;
public class PresentationUploadToken implements IMessage { public class PresentationUploadToken implements IMessage {
public final String podId; public final String podId;
public final String presentationId;
public final String authzToken; public final String authzToken;
public final String filename; public final String filename;
public final String meetingId; 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.podId = podId;
this.authzToken = authzToken; this.authzToken = authzToken;
this.presentationId = presentationId;
this.filename = filename; this.filename = filename;
this.meetingId = meetingId; this.meetingId = meetingId;
} }

View File

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

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