Merge pull request #19310 from antobinary/merge-dec-7
chore: Merge BBB 3.0.0-alpha.1 into develop
This commit is contained in:
commit
ecaf828c77
22
.github/workflows/automated-tests.yml
vendored
22
.github/workflows/automated-tests.yml
vendored
@ -13,6 +13,7 @@ on:
|
||||
paths-ignore:
|
||||
- "docs/**"
|
||||
- "**/*.md"
|
||||
- "bigbluebutton-html5/public/locales/*.json"
|
||||
permissions:
|
||||
contents: read
|
||||
concurrency:
|
||||
@ -53,7 +54,7 @@ jobs:
|
||||
build-list: bbb-playback bbb-playback-notes bbb-playback-podcast bbb-playback-presentation bbb-playback-screenshare bbb-playback-video bbb-record-core
|
||||
- package: bbb-graphql-server
|
||||
build-name: bbb-graphql-server
|
||||
build-list: bbb-graphql-server bbb-graphql-middleware
|
||||
build-list: bbb-graphql-server bbb-graphql-middleware bbb-graphql-actions-adapter-server
|
||||
- package: bbb-etherpad
|
||||
cache-files-list: bbb-etherpad.placeholder.sh
|
||||
cache-urls-list: https://api.github.com/repos/mconf/ep_pad_ttl/commits https://api.github.com/repos/alangecker/bbb-etherpad-plugin/commits https://api.github.com/repos/mconf/ep_redis_publisher/commits https://api.github.com/repos/alangecker/bbb-etherpad-skin/commits
|
||||
@ -72,7 +73,7 @@ jobs:
|
||||
build-list: bbb-webrtc-sfu bbb-webrtc-recorder
|
||||
cache-files-list: bbb-webrtc-sfu.placeholder.sh bbb-webrtc-recorder.placeholder.sh
|
||||
- package: others
|
||||
build-list: bbb-mkclean bbb-pads bbb-libreoffice-docker bbb-transcription-controller bigbluebutton
|
||||
build-list: bbb-mkclean bbb-pads bbb-libreoffice-docker bbb-transcription-controller bigbluebutton bbb-livekit
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Merge branches
|
||||
@ -252,7 +253,7 @@ jobs:
|
||||
apt --purge -y remove apache2-bin
|
||||
'
|
||||
- name: Install BBB
|
||||
timeout-minutes: 15
|
||||
timeout-minutes: 25
|
||||
run: |
|
||||
sudo -i <<EOF
|
||||
set -e
|
||||
@ -263,6 +264,12 @@ jobs:
|
||||
sed -i "s/\"minify\": true,/\"minify\": false,/" /usr/share/etherpad-lite/settings.json
|
||||
bbb-conf --restart
|
||||
EOF
|
||||
- name: List systemctl services
|
||||
timeout-minutes: 1
|
||||
run: |
|
||||
sudo -i <<EOF
|
||||
systemctl --type=service --state=running,exited,failed --all --no-pager --no-legend
|
||||
EOF
|
||||
- name: Install test dependencies
|
||||
working-directory: ./bigbluebutton-tests/playwright
|
||||
run: |
|
||||
@ -272,13 +279,18 @@ jobs:
|
||||
npx playwright install
|
||||
'
|
||||
- name: Run tests
|
||||
working-directory: ./bigbluebutton-tests/playwright
|
||||
uses: nick-fields/retry@v2
|
||||
with:
|
||||
timeout_minutes: 25
|
||||
max_attempts: 3
|
||||
command: |
|
||||
cd ./bigbluebutton-tests/playwright
|
||||
npm run test-chromium-ci -- --shard ${{ matrix.shard }}
|
||||
env:
|
||||
NODE_EXTRA_CA_CERTS: /usr/local/share/ca-certificates/bbb-dev/bbb-dev-ca.crt
|
||||
ACTIONS_RUNNER_DEBUG: true
|
||||
BBB_URL: https://bbb-ci.test/bigbluebutton/api
|
||||
BBB_SECRET: bbbci
|
||||
run: npm run test-chromium-ci -- --shard ${{ matrix.shard }}
|
||||
- name: Run Firefox tests
|
||||
working-directory: ./bigbluebutton-tests/playwright
|
||||
if: |
|
||||
|
@ -12,7 +12,7 @@ stages:
|
||||
|
||||
# define which docker image to use for builds
|
||||
default:
|
||||
image: bigbluebutton/bbb-build:bbb28-2023-07-18
|
||||
image: bigbluebutton/bbb-build:v3.0.x-release--2023-09-26-152524
|
||||
|
||||
# This stage uses git to find out since when each package has been unmodified.
|
||||
# it then checks an API endpoint on the package server to find out for which of
|
||||
@ -182,6 +182,11 @@ bbb-webrtc-recorder-build:
|
||||
script:
|
||||
- build/setup-inside-docker.sh bbb-webrtc-recorder
|
||||
|
||||
bbb-livekit:
|
||||
extends: .build_job
|
||||
script:
|
||||
- build/setup-inside-docker.sh bbb-livekit
|
||||
|
||||
bbb-transcription-controller-build:
|
||||
extends: .build_job
|
||||
script:
|
||||
|
@ -9,7 +9,7 @@ We actively support BigBlueButton through the community forums and through secur
|
||||
| 2.5.x (or earlier) | :x: |
|
||||
| 2.6.x | :white_check_mark: |
|
||||
| 2.7.x | :white_check_mark: |
|
||||
| 2.8.x | :x: |
|
||||
| 3.0.x | :x: |
|
||||
|
||||
We have released 2.7 to the community and are going to support both 2.6 and 2.7 together for the coming months (while we're actively developing the next release). Also, BigBlueButton 2.5 is now end of life.
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
package org.bigbluebutton.build
|
||||
|
||||
import sbt._
|
||||
import Keys._
|
||||
|
||||
object Dependencies {
|
||||
|
||||
@ -37,6 +36,7 @@ object Dependencies {
|
||||
val scalaTest = "3.2.11"
|
||||
val mockito = "2.23.0"
|
||||
val akkaTestKit = "2.6.0"
|
||||
val jacksonDataFormat = "2.13.5"
|
||||
}
|
||||
|
||||
object Compile {
|
||||
@ -64,7 +64,11 @@ object Dependencies {
|
||||
val slick = "com.typesafe.slick" %% "slick" % Versions.slick
|
||||
val slickHikaricp = "com.typesafe.slick" %% "slick-hikaricp" % Versions.slick
|
||||
val slickPg = "com.github.tminglei" %% "slick-pg" % Versions.slickPg
|
||||
val slickPgSprayJson = "com.github.tminglei" %% "slick-pg_spray-json" % Versions.slickPg
|
||||
|
||||
val postgresql = "org.postgresql" % "postgresql" % Versions.postgresql
|
||||
val jacksonDataFormat = "com.fasterxml.jackson.dataformat" % "jackson-dataformat-yaml" % Versions.jacksonDataFormat
|
||||
val snakeYaml = "org.yaml" % "snakeyaml"
|
||||
}
|
||||
|
||||
object Test {
|
||||
@ -101,5 +105,7 @@ object Dependencies {
|
||||
Compile.slick,
|
||||
Compile.slickHikaricp,
|
||||
Compile.slickPg,
|
||||
Compile.postgresql) ++ testing
|
||||
Compile.slickPgSprayJson,
|
||||
Compile.postgresql,
|
||||
Compile.jacksonDataFormat) ++ testing
|
||||
}
|
||||
|
@ -4,17 +4,15 @@ import org.apache.pekko.actor.ActorSystem
|
||||
import org.apache.pekko.event.Logging
|
||||
import org.apache.pekko.http.scaladsl.Http
|
||||
import org.apache.pekko.stream.ActorMaterializer
|
||||
import org.bigbluebutton.common2.redis.{ MessageSender, RedisConfig, RedisPublisher }
|
||||
import org.bigbluebutton.common2.redis.{MessageSender, RedisConfig, RedisPublisher}
|
||||
import org.bigbluebutton.core._
|
||||
import org.bigbluebutton.core.bus._
|
||||
import org.bigbluebutton.core.pubsub.senders.ReceivedJsonMsgHandlerActor
|
||||
import org.bigbluebutton.core2.AnalyticsActor
|
||||
import org.bigbluebutton.core2.FromAkkaAppsMsgSenderActor
|
||||
import org.bigbluebutton.endpoint.redis.AppsRedisSubscriberActor
|
||||
import org.bigbluebutton.endpoint.redis.{ RedisRecorderActor, ExportAnnotationsActor }
|
||||
import org.bigbluebutton.endpoint.redis.LearningDashboardActor
|
||||
import org.bigbluebutton.endpoint.redis.{AppsRedisSubscriberActor, ExportAnnotationsActor, GraphqlActionsActor, LearningDashboardActor, RedisRecorderActor}
|
||||
import org.bigbluebutton.common2.bus.IncomingJsonMessageBus
|
||||
import org.bigbluebutton.service.{ HealthzService, MeetingInfoActor, MeetingInfoService }
|
||||
import org.bigbluebutton.service.{HealthzService, MeetingInfoActor, MeetingInfoService}
|
||||
|
||||
object Boot extends App with SystemConfiguration {
|
||||
|
||||
@ -69,6 +67,12 @@ object Boot extends App with SystemConfiguration {
|
||||
"LearningDashboardActor"
|
||||
)
|
||||
|
||||
val graphqlActionsActor = system.actorOf(
|
||||
GraphqlActionsActor.props(system),
|
||||
"GraphqlActionsActor"
|
||||
)
|
||||
|
||||
ClientSettings.loadClientSettingsFromFile()
|
||||
recordingEventBus.subscribe(redisRecorderActor, outMessageChannel)
|
||||
val incomingJsonMessageBus = new IncomingJsonMessageBus
|
||||
|
||||
@ -85,6 +89,9 @@ object Boot extends App with SystemConfiguration {
|
||||
outBus2.subscribe(learningDashboardActor, outBbbMsgMsgChannel)
|
||||
bbbMsgBus.subscribe(learningDashboardActor, analyticsChannel)
|
||||
|
||||
eventBus.subscribe(graphqlActionsActor, meetingManagerChannel)
|
||||
bbbMsgBus.subscribe(graphqlActionsActor, analyticsChannel)
|
||||
|
||||
val bbbActor = system.actorOf(BigBlueButtonActor.props(system, eventBus, bbbMsgBus, outGW, healthzService), "bigbluebutton-actor")
|
||||
eventBus.subscribe(bbbActor, meetingManagerChannel)
|
||||
|
||||
|
@ -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])
|
||||
|
||||
}
|
@ -1,6 +1,9 @@
|
||||
package org.bigbluebutton
|
||||
|
||||
import scala.util.Try
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
|
||||
|
||||
import scala.util.{ Failure, Success, Try }
|
||||
import com.typesafe.config.ConfigFactory
|
||||
|
||||
trait SystemConfiguration {
|
||||
@ -77,6 +80,13 @@ trait SystemConfiguration {
|
||||
|
||||
lazy val analyticsIncludeChat = Try(config.getBoolean("analytics.includeChat")).getOrElse(true)
|
||||
|
||||
lazy val clientSettingsPath = Try(config.getString("client.clientSettingsFilePath")).getOrElse(
|
||||
"/usr/share/meteor/bundle/programs/server/assets/app/config/settings.yml"
|
||||
)
|
||||
lazy val clientSettingsPathOverride = Try(config.getString("client.clientSettingsOverrideFilePath")).getOrElse(
|
||||
"/etc/bigbluebutton/bbb-html5.yml"
|
||||
)
|
||||
|
||||
// Grab the "interface" parameter from the http config
|
||||
val httpHost = config.getString("http.interface")
|
||||
// Grab the "port" parameter from the http config
|
||||
|
@ -81,6 +81,8 @@ class BigBlueButtonActor(
|
||||
case m: GetRunningMeetingsReqMsg => handleGetRunningMeetingsReqMsg(m)
|
||||
case m: CheckAlivePingSysMsg => handleCheckAlivePingSysMsg(m)
|
||||
case m: ValidateConnAuthTokenSysMsg => handleValidateConnAuthTokenSysMsg(m)
|
||||
case _: UserGraphqlConnectionStablishedSysMsg => //Ignore
|
||||
case _: UserGraphqlConnectionClosedSysMsg => //Ignore
|
||||
case _ => log.warning("Cannot handle " + msg.envelope.name)
|
||||
}
|
||||
}
|
||||
|
@ -113,6 +113,12 @@ case class EjectUserFromBreakoutInternalMsg(parentId: String, breakoutId: String
|
||||
*/
|
||||
case class CapturePresentationReqInternalMsg(userId: String, parentMeetingId: String, filename: String, allPages: Boolean = true) extends InMessage
|
||||
|
||||
/**
|
||||
* Sent to the same meeting to force a new presenter to the Pod
|
||||
* @param presenterId
|
||||
*/
|
||||
case class SetPresenterInDefaultPodInternalMsg(presenterId: String) extends InMessage
|
||||
|
||||
/**
|
||||
* Sent by breakout room to parent meeting to obtain padId
|
||||
* @param breakoutId
|
||||
|
@ -16,7 +16,7 @@ object TimerModel {
|
||||
|
||||
def reset(model: TimerModel) : Unit = {
|
||||
model.accumulated = 0
|
||||
model.startedAt = 0
|
||||
model.startedAt = if (model.running) System.currentTimeMillis() else 0
|
||||
model.endedAt = 0
|
||||
}
|
||||
|
||||
@ -24,7 +24,7 @@ object TimerModel {
|
||||
model.isActive = active
|
||||
}
|
||||
|
||||
def getIsACtive(model: TimerModel): Boolean = {
|
||||
def getIsActive(model: TimerModel): Boolean = {
|
||||
model.isActive
|
||||
}
|
||||
|
||||
|
@ -2,8 +2,8 @@ package org.bigbluebutton.core.apps.audiocaptions
|
||||
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.bus.MessageBus
|
||||
import org.bigbluebutton.core.db.AudioCaptionDAO
|
||||
import org.bigbluebutton.core.models.AudioCaptions
|
||||
import org.bigbluebutton.core.db.CaptionDAO
|
||||
import org.bigbluebutton.core.models.{AudioCaptions, Users2x}
|
||||
import org.bigbluebutton.core.running.LiveMeeting
|
||||
|
||||
import java.sql.Timestamp
|
||||
@ -66,7 +66,12 @@ trait UpdateTranscriptPubMsgHdlr {
|
||||
|
||||
val transcript = AudioCaptions.parseTranscript(msg.body.transcript)
|
||||
|
||||
AudioCaptionDAO.insertOrUpdateAudioCaption(msg.body.transcriptId, meetingId, msg.header.userId, transcript, new Timestamp(System.currentTimeMillis()))
|
||||
|
||||
for {
|
||||
u <- Users2x.findWithIntId(liveMeeting.users2x, msg.header.userId)
|
||||
} yield {
|
||||
CaptionDAO.insertOrUpdateAudioCaption(msg.body.transcriptId, meetingId, msg.header.userId, transcript, u.speechLocale)
|
||||
}
|
||||
|
||||
broadcastEvent(
|
||||
msg.header.userId,
|
||||
|
@ -70,7 +70,7 @@ trait CreateBreakoutRoomsCmdMsgHdlr extends RightsManagementTrait {
|
||||
breakout.freeJoin,
|
||||
liveMeeting.props.voiceProp.dialNumber,
|
||||
breakout.voiceConf,
|
||||
msg.body.durationInMinutes * 60,
|
||||
msg.body.durationInMinutes,
|
||||
liveMeeting.props.password.moderatorPass,
|
||||
liveMeeting.props.password.viewerPass,
|
||||
presId, presSlide, msg.body.record,
|
||||
|
@ -4,7 +4,7 @@ import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.api.{ SendTimeRemainingAuditInternalMsg, UpdateBreakoutRoomTimeInternalMsg }
|
||||
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
|
||||
import org.bigbluebutton.core.bus.BigBlueButtonEvent
|
||||
import org.bigbluebutton.core.db.BreakoutRoomDAO
|
||||
import org.bigbluebutton.core.db.{ BreakoutRoomDAO, MeetingDAO }
|
||||
import org.bigbluebutton.core.domain.MeetingState2x
|
||||
import org.bigbluebutton.core.running.{ MeetingActor, OutMsgRouter }
|
||||
import org.bigbluebutton.core2.message.senders.{ MsgBuilder, Sender }
|
||||
@ -78,6 +78,7 @@ trait UpdateBreakoutRoomsTimeMsgHdlr extends RightsManagementTrait {
|
||||
|
||||
log.debug("Updating {} minutes for breakout rooms time in meeting {}", msg.body.timeInMinutes, props.meetingProp.intId)
|
||||
BreakoutRoomDAO.updateRoomsDuration(props.meetingProp.intId, newDurationInSeconds)
|
||||
MeetingDAO.updateMeetingDurationByParentMeeting(props.meetingProp.intId, newDurationInSeconds)
|
||||
breakoutModel.setTime(newDurationInSeconds)
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package org.bigbluebutton.core.apps.pads
|
||||
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.bus.MessageBus
|
||||
import org.bigbluebutton.core.db.CaptionDAO
|
||||
import org.bigbluebutton.core.models.Pads
|
||||
import org.bigbluebutton.core.running.LiveMeeting
|
||||
|
||||
@ -40,6 +41,7 @@ trait PadPatchSysMsgHdlr {
|
||||
broadcastEditCaptionHistoryEvent(msg.body.userId, msg.body.start, msg.body.end, group.name, locale, msg.body.text)
|
||||
val tail = liveMeeting.captionModel.getTextTail(group.name)
|
||||
broadcastPadTailEvent(group.externalId, tail)
|
||||
CaptionDAO.insertOrUpdatePadCaption(liveMeeting.props.meetingProp.intId, locale, msg.body.userId, tail)
|
||||
}
|
||||
}
|
||||
case _ =>
|
||||
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
@ -6,7 +6,7 @@ import org.bigbluebutton.core.apps.groupchats.GroupChatApp
|
||||
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
|
||||
import org.bigbluebutton.core.apps.presentationpod.PresentationSender
|
||||
import org.bigbluebutton.core.bus.MessageBus
|
||||
import org.bigbluebutton.core.db.ChatMessageDAO
|
||||
import org.bigbluebutton.core.db.{ ChatMessageDAO, PresPresentationDAO }
|
||||
import org.bigbluebutton.core.domain.MeetingState2x
|
||||
import org.bigbluebutton.core.running.LiveMeeting
|
||||
import org.bigbluebutton.core.util.RandomStringGenerator
|
||||
@ -53,12 +53,13 @@ trait MakePresentationDownloadReqMsgHdlr extends RightsManagementTrait {
|
||||
|
||||
def buildBroadcastNewPresFileAvailable(newPresFileAvailableMsg: NewPresFileAvailableMsg, liveMeeting: LiveMeeting): BbbCommonEnvCoreMsg = {
|
||||
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, liveMeeting.props.meetingProp.intId, "not-used")
|
||||
val envelope = BbbCoreEnvelope(PresentationPageConvertedEventMsg.NAME, routing)
|
||||
val envelope = BbbCoreEnvelope(NewPresFileAvailableEvtMsg.NAME, routing)
|
||||
val header = BbbClientMsgHeader(NewPresFileAvailableEvtMsg.NAME, liveMeeting.props.meetingProp.intId, "not-used")
|
||||
val body = NewPresFileAvailableEvtMsgBody(
|
||||
annotatedFileURI = newPresFileAvailableMsg.body.annotatedFileURI,
|
||||
originalFileURI = newPresFileAvailableMsg.body.originalFileURI,
|
||||
convertedFileURI = newPresFileAvailableMsg.body.convertedFileURI, presId = newPresFileAvailableMsg.body.presId,
|
||||
convertedFileURI = newPresFileAvailableMsg.body.convertedFileURI,
|
||||
presId = newPresFileAvailableMsg.body.presId,
|
||||
fileStateType = newPresFileAvailableMsg.body.fileStateType
|
||||
)
|
||||
val event = NewPresFileAvailableEvtMsg(header, body)
|
||||
@ -85,11 +86,11 @@ trait MakePresentationDownloadReqMsgHdlr extends RightsManagementTrait {
|
||||
BbbCommonEnvCoreMsg(envelope, event)
|
||||
}
|
||||
|
||||
def buildPresentationUploadTokenSysPubMsg(parentId: String, userId: String, presentationUploadToken: String, filename: String): BbbCommonEnvCoreMsg = {
|
||||
def buildPresentationUploadTokenSysPubMsg(parentMeetingId: String, userId: String, presentationUploadToken: String, filename: String, presId: String): BbbCommonEnvCoreMsg = {
|
||||
val routing = collection.immutable.HashMap("sender" -> "bbb-apps-akka")
|
||||
val envelope = BbbCoreEnvelope(PresentationUploadTokenSysPubMsg.NAME, routing)
|
||||
val header = BbbClientMsgHeader(PresentationUploadTokenSysPubMsg.NAME, parentId, userId)
|
||||
val body = PresentationUploadTokenSysPubMsgBody("DEFAULT_PRESENTATION_POD", presentationUploadToken, filename, parentId)
|
||||
val header = BbbClientMsgHeader(PresentationUploadTokenSysPubMsg.NAME, parentMeetingId, userId)
|
||||
val body = PresentationUploadTokenSysPubMsgBody("DEFAULT_PRESENTATION_POD", presentationUploadToken, filename, parentMeetingId, presId)
|
||||
val event = PresentationUploadTokenSysPubMsg(header, body)
|
||||
BbbCommonEnvCoreMsg(envelope, event)
|
||||
}
|
||||
@ -133,9 +134,18 @@ trait MakePresentationDownloadReqMsgHdlr extends RightsManagementTrait {
|
||||
|
||||
val currentPres: Option[PresentationInPod] = presentationPods.flatMap(_.getPresentation(presId)).headOption
|
||||
|
||||
if (liveMeeting.props.meetingProp.disabledFeatures.contains("downloadPresentationWithAnnotations")) {
|
||||
if (liveMeeting.props.meetingProp.disabledFeatures.contains("downloadPresentationWithAnnotations")
|
||||
&& m.body.fileStateType == "Annotated") {
|
||||
val reason = "Annotated presentation download disabled for this meeting."
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, userId, reason, bus.outGW, liveMeeting)
|
||||
} else if (liveMeeting.props.meetingProp.disabledFeatures.contains("downloadPresentationOriginalFile")
|
||||
&& m.body.fileStateType == "Original") {
|
||||
val reason = "Original presentation download disabled for this meeting."
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, userId, reason, bus.outGW, liveMeeting)
|
||||
} else if (liveMeeting.props.meetingProp.disabledFeatures.contains("downloadPresentationConvertedToPdf")
|
||||
&& m.body.fileStateType == "Converted") {
|
||||
val reason = "Converted presentation download disabled for this meeting. (PDF format)"
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, userId, reason, bus.outGW, liveMeeting)
|
||||
} else if (permissionFailed(PermissionCheck.MOD_LEVEL, PermissionCheck.VIEWER_LEVEL, liveMeeting.users2x, userId)) {
|
||||
val reason = "No permission to download presentation."
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, userId, reason, bus.outGW, liveMeeting)
|
||||
@ -196,9 +206,10 @@ trait MakePresentationDownloadReqMsgHdlr extends RightsManagementTrait {
|
||||
|
||||
val filename = m.filename
|
||||
val presentationUploadToken: String = PresentationPodsApp.generateToken("DEFAULT_PRESENTATION_POD", userId)
|
||||
val presentationId = PresentationPodsApp.generatePresentationId(m.filename)
|
||||
|
||||
// Informs bbb-web about the token so that when we use it to upload the presentation, it is able to look it up in the list of tokens
|
||||
bus.outGW.send(buildPresentationUploadTokenSysPubMsg(parentMeetingId, userId, presentationUploadToken, filename))
|
||||
bus.outGW.send(buildPresentationUploadTokenSysPubMsg(parentMeetingId, userId, presentationUploadToken, filename, presentationId))
|
||||
|
||||
if (liveMeeting.props.meetingProp.disabledFeatures.contains("importPresentationWithAnnotationsFromBreakoutRooms")) {
|
||||
log.error(s"Capturing breakout rooms slides disabled in meeting ${meetingId}.")
|
||||
@ -215,7 +226,7 @@ trait MakePresentationDownloadReqMsgHdlr extends RightsManagementTrait {
|
||||
val currentPage: PresentationPage = PresentationInPod.getCurrentPage(currentPres.get).get
|
||||
val pagesRange: List[Int] = if (allPages) (1 to pageCount).toList else List(currentPage.num)
|
||||
|
||||
val exportJob: ExportJob = new ExportJob(jobId, JobTypes.CAPTURE_PRESENTATION, filename, presId, presLocation, allPages, pagesRange, parentMeetingId, presentationUploadToken)
|
||||
val exportJob: ExportJob = ExportJob(jobId, JobTypes.CAPTURE_PRESENTATION, filename, presId, presLocation, allPages, pagesRange, parentMeetingId, presentationUploadToken)
|
||||
val storeAnnotationPages: List[PresentationPageForExport] = getPresentationPagesForExport(pagesRange, pageCount, presId, currentPres, liveMeeting);
|
||||
|
||||
val annotationCount: Int = storeAnnotationPages.map(_.annotations.size).sum
|
||||
@ -248,8 +259,13 @@ trait MakePresentationDownloadReqMsgHdlr extends RightsManagementTrait {
|
||||
"filename" -> "annotated_slides.pdf"
|
||||
)
|
||||
ChatMessageDAO.insertSystemMsg(liveMeeting.props.meetingProp.intId, GroupChatApp.MAIN_PUBLIC_CHAT, "", GroupChatMessageType.PRESENTATION, presentationDownloadInfo, "")
|
||||
} else if (m.body.fileStateType == "Converted") {
|
||||
PresPresentationDAO.updateDownloadUri(m.body.presId, m.body.convertedFileURI)
|
||||
} else if (m.body.fileStateType == "Original") {
|
||||
PresPresentationDAO.updateDownloadUri(m.body.presId, m.body.originalFileURI)
|
||||
}
|
||||
|
||||
PresPresentationDAO.updateExportToChatStatus(m.body.presId, "EXPORTED")
|
||||
bus.outGW.send(buildBroadcastNewPresFileAvailable(m, liveMeeting))
|
||||
}
|
||||
|
||||
@ -265,6 +281,7 @@ trait MakePresentationDownloadReqMsgHdlr extends RightsManagementTrait {
|
||||
}
|
||||
|
||||
def handle(m: PresAnnStatusMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
|
||||
PresPresentationDAO.updateExportToChat(m.body.presId, m.body.status, m.body.pageNumber, m.body.error)
|
||||
bus.outGW.send(buildBroadcastPresAnnStatusMsg(m, liveMeeting))
|
||||
}
|
||||
|
||||
@ -274,8 +291,9 @@ trait MakePresentationDownloadReqMsgHdlr extends RightsManagementTrait {
|
||||
val jobId: String = s"${m.body.breakoutId}-notes" // Used as the temporaryPresentationId upon upload
|
||||
val filename = m.body.filename
|
||||
val presentationUploadToken: String = PresentationPodsApp.generateToken("DEFAULT_PRESENTATION_POD", userId)
|
||||
val presentationId = PresentationPodsApp.generatePresentationId(m.body.filename)
|
||||
|
||||
bus.outGW.send(buildPresentationUploadTokenSysPubMsg(m.body.parentMeetingId, userId, presentationUploadToken, filename))
|
||||
bus.outGW.send(buildPresentationUploadTokenSysPubMsg(m.body.parentMeetingId, userId, presentationUploadToken, filename, presentationId))
|
||||
|
||||
val exportJob = new ExportJob(jobId, JobTypes.CAPTURE_NOTES, filename, m.body.padId, "", true, List(), m.body.parentMeetingId, presentationUploadToken)
|
||||
val job = buildStoreExportJobInRedisSysMsg(exportJob, liveMeeting)
|
||||
|
@ -41,21 +41,21 @@ trait PdfConversionInvalidErrorSysPubMsgHdlr {
|
||||
pod <- PresentationPodsApp.getPresentationPod(state, msg.body.podId)
|
||||
pres <- pod.getPresentation(msg.body.presentationId)
|
||||
} yield {
|
||||
val presWithError = PresentationInPod(pres.id, pres.name, pres.current, pres.pages, pres.downloadable, pres.removable,
|
||||
pres.filenameConverted, pres.uploadCompleted, pres.numPages, msg.body.messageKey, errorDetails)
|
||||
val presWithError = PresentationInPod(pres.id, pres.name, pres.default, pres.current, pres.pages, pres.downloadable,
|
||||
pres.downloadFileExtension, pres.removable, pres.filenameConverted, pres.uploadCompleted, pres.numPages,
|
||||
msg.body.messageKey, errorDetails)
|
||||
var pods = state.presentationPodManager.addPod(pod)
|
||||
pods = pods.addPresentationToPod(pod.id, presWithError)
|
||||
PresPresentationDAO.insertOrUpdate(msg.header.meetingId, presWithError)
|
||||
|
||||
state.update(pods)
|
||||
}
|
||||
|
||||
PresPresentationDAO.updateErrors(msg.body.presentationId, msg.body.messageKey, errorDetails)
|
||||
broadcastEvent(msg)
|
||||
|
||||
newState match {
|
||||
case Some(ns) => ns
|
||||
case None =>
|
||||
PresPresentationDAO.updateErrors(msg.body.presentationId, msg.body.messageKey, errorDetails)
|
||||
state
|
||||
case None => state
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,12 +8,13 @@ import org.bigbluebutton.core.models.PresentationInPod
|
||||
import org.bigbluebutton.core.running.LiveMeeting
|
||||
|
||||
trait PresentationConversionCompletedSysPubMsgHdlr {
|
||||
|
||||
this: PresentationPodHdlrs =>
|
||||
|
||||
def handle(
|
||||
msg: PresentationConversionCompletedSysPubMsg, state: MeetingState2x,
|
||||
liveMeeting: LiveMeeting, bus: MessageBus
|
||||
msg: PresentationConversionCompletedSysPubMsg,
|
||||
state: MeetingState2x,
|
||||
liveMeeting: LiveMeeting,
|
||||
bus: MessageBus
|
||||
): MeetingState2x = {
|
||||
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
@ -24,7 +25,7 @@ trait PresentationConversionCompletedSysPubMsgHdlr {
|
||||
pres <- pod.getPresentation(msg.body.presentation.id)
|
||||
} yield {
|
||||
val presVO = PresentationPodsApp.translatePresentationToPresentationVO(pres, temporaryPresentationId,
|
||||
msg.body.presentation.isInitialPresentation, msg.body.presentation.filenameConverted)
|
||||
msg.body.presentation.defaultPresentation, msg.body.presentation.filenameConverted)
|
||||
PresentationSender.broadcastPresentationConversionCompletedEvtMsg(
|
||||
bus,
|
||||
meetingId,
|
||||
@ -47,13 +48,13 @@ trait PresentationConversionCompletedSysPubMsgHdlr {
|
||||
originalDownloadableExtension
|
||||
)
|
||||
|
||||
val presWithConvertedName = PresentationInPod(pres.id, pres.name, pres.current, pres.pages,
|
||||
pres.downloadable, pres.removable, msg.body.presentation.filenameConverted, uploadCompleted = true, numPages = pres.numPages, errorDetails = Map.empty)
|
||||
val presWithConvertedName = PresentationInPod(pres.id, pres.name, default = msg.body.presentation.defaultPresentation,
|
||||
pres.current, pres.pages, pres.downloadable, pres.downloadFileExtension, pres.removable, msg.body.presentation.filenameConverted,
|
||||
uploadCompleted = true, numPages = pres.numPages, errorDetails = Map.empty)
|
||||
var pods = state.presentationPodManager.addPod(pod)
|
||||
pods = pods.addPresentationToPod(pod.id, presWithConvertedName)
|
||||
|
||||
PresPresentationDAO.insertOrUpdate(meetingId, presWithConvertedName)
|
||||
|
||||
PresPresentationDAO.updatePages(presWithConvertedName)
|
||||
state.update(pods)
|
||||
}
|
||||
|
||||
|
@ -37,10 +37,6 @@ trait PresentationConversionUpdatePubMsgHdlr {
|
||||
bus.outGW.send(msgEvent)
|
||||
}
|
||||
|
||||
val pres = new PresentationInPod(msg.body.presentationId, msg.body.presName, false, Map.empty, false,
|
||||
false, uploadCompleted = false, numPages = -1, errorDetails = Map.empty)
|
||||
PresPresentationDAO.insertOrUpdate(msg.header.meetingId, pres)
|
||||
|
||||
broadcastEvent(msg)
|
||||
state
|
||||
}
|
||||
|
@ -39,11 +39,9 @@ trait PresentationHasInvalidMimeTypeErrorPubMsgHdlr {
|
||||
"fileExtension" -> msg.body.fileExtension
|
||||
)
|
||||
|
||||
val pres = new PresentationInPod(msg.body.presentationId, msg.body.presentationName, false, Map.empty, false,
|
||||
false, uploadCompleted = false, numPages = -1, errorMsgKey = msg.body.messageKey, errorDetails = errorDetails)
|
||||
PresPresentationDAO.insertOrUpdate(msg.header.meetingId, pres)
|
||||
|
||||
PresPresentationDAO.updateErrors(msg.body.presentationId, msg.body.messageKey, errorDetails)
|
||||
broadcastEvent(msg)
|
||||
|
||||
state
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,9 @@ trait PresentationPageConversionStartedSysMsgHdlr {
|
||||
podId = msg.body.podId,
|
||||
presentationId = msg.body.presentationId,
|
||||
current = msg.body.current,
|
||||
default = msg.body.default,
|
||||
presName = msg.body.presName,
|
||||
presFilenameConverted = msg.body.presFilenameConverted,
|
||||
downloadable = msg.body.downloadable,
|
||||
removable = msg.body.removable,
|
||||
authzToken = msg.body.authzToken,
|
||||
@ -44,8 +46,8 @@ trait PresentationPageConversionStartedSysMsgHdlr {
|
||||
val presentationId = msg.body.presentationId
|
||||
val podId = msg.body.podId
|
||||
|
||||
val pres = new PresentationInPod(presentationId, msg.body.presName, msg.body.current, Map.empty, downloadable,
|
||||
removable, uploadCompleted = false, numPages = msg.body.numPages, errorDetails = Map.empty)
|
||||
val pres = new PresentationInPod(presentationId, msg.body.presName, msg.body.default, msg.body.current, Map.empty, downloadable,
|
||||
"", removable, filenameConverted = msg.body.presFilenameConverted, uploadCompleted = false, numPages = msg.body.numPages, errorDetails = Map.empty)
|
||||
|
||||
val newState = for {
|
||||
pod <- PresentationPodsApp.getPresentationPod(state, podId)
|
||||
@ -59,6 +61,7 @@ trait PresentationPageConversionStartedSysMsgHdlr {
|
||||
state.update(pods)
|
||||
}
|
||||
|
||||
PresPresentationDAO.updateConversionStarted(liveMeeting.props.meetingProp.intId, pres)
|
||||
broadcastEvent(msg)
|
||||
|
||||
newState match {
|
||||
|
@ -3,7 +3,7 @@ package org.bigbluebutton.core.apps.presentationpod
|
||||
import org.bigbluebutton.common2.domain.PresentationPageVO
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.bus.MessageBus
|
||||
import org.bigbluebutton.core.db.PresPresentationDAO
|
||||
import org.bigbluebutton.core.db.{ PresPageDAO, PresPresentationDAO }
|
||||
import org.bigbluebutton.core.domain.MeetingState2x
|
||||
import org.bigbluebutton.core.models.{ PresentationInPod, PresentationPage }
|
||||
import org.bigbluebutton.core.running.LiveMeeting
|
||||
@ -55,6 +55,7 @@ trait PresentationPageConvertedSysMsgHdlr {
|
||||
msg.body.page.id,
|
||||
msg.body.page.num,
|
||||
msg.body.page.urls,
|
||||
msg.body.page.content,
|
||||
msg.body.page.current,
|
||||
width = msg.body.page.width,
|
||||
height = msg.body.page.height,
|
||||
@ -69,7 +70,7 @@ trait PresentationPageConvertedSysMsgHdlr {
|
||||
var pods = state.presentationPodManager.addPod(pod)
|
||||
pods = pods.addPresentationToPod(pod.id, newPres)
|
||||
|
||||
PresPresentationDAO.insertOrUpdate(msg.header.meetingId, newPres)
|
||||
PresPageDAO.insert(pres.id, page)
|
||||
state.update(pods)
|
||||
}
|
||||
|
||||
|
@ -41,21 +41,20 @@ trait PresentationPageCountErrorPubMsgHdlr {
|
||||
pod <- PresentationPodsApp.getPresentationPod(state, msg.body.podId)
|
||||
pres <- pod.getPresentation(msg.body.presentationId)
|
||||
} yield {
|
||||
val presWithError = PresentationInPod(pres.id, pres.name, pres.current, pres.pages, pres.downloadable, pres.removable,
|
||||
pres.filenameConverted, pres.uploadCompleted, msg.body.numberOfPages, msg.body.messageKey, errorDetails)
|
||||
val presWithError = PresentationInPod(pres.id, pres.name, pres.default, pres.current, pres.pages, pres.downloadable,
|
||||
"", pres.removable, pres.filenameConverted, pres.uploadCompleted, msg.body.numberOfPages, msg.body.messageKey, errorDetails)
|
||||
var pods = state.presentationPodManager.addPod(pod)
|
||||
pods = pods.addPresentationToPod(pod.id, presWithError)
|
||||
PresPresentationDAO.insertOrUpdate(msg.header.meetingId, presWithError)
|
||||
|
||||
state.update(pods)
|
||||
}
|
||||
|
||||
PresPresentationDAO.updateErrors(msg.body.presentationId, msg.body.messageKey, errorDetails)
|
||||
broadcastEvent(msg)
|
||||
|
||||
newState match {
|
||||
case Some(ns) => ns
|
||||
case None =>
|
||||
PresPresentationDAO.updateErrors(msg.body.presentationId, msg.body.messageKey, errorDetails)
|
||||
state
|
||||
case None => state
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ class PresentationPodHdlrs(implicit val context: ActorContext)
|
||||
with PresentationConversionCompletedSysPubMsgHdlr
|
||||
with PdfConversionInvalidErrorSysPubMsgHdlr
|
||||
with SetCurrentPagePubMsgHdlr
|
||||
with SetPresenterInPodReqMsgHdlr
|
||||
with SetPresenterInDefaultPodInternalMsgHdlr
|
||||
with RemovePresentationPubMsgHdlr
|
||||
with SetPresentationDownloadablePubMsgHdlr
|
||||
with PresentationConversionUpdatePubMsgHdlr
|
||||
|
@ -1,5 +1,6 @@
|
||||
package org.bigbluebutton.core.apps.presentationpod
|
||||
|
||||
import org.apache.commons.codec.digest.DigestUtils
|
||||
import org.bigbluebutton.common2.domain._
|
||||
import org.bigbluebutton.core.domain._
|
||||
import org.bigbluebutton.core.models._
|
||||
@ -74,7 +75,7 @@ object PresentationPodsApp {
|
||||
}
|
||||
|
||||
def translatePresentationToPresentationVO(pres: PresentationInPod, temporaryPresentationId: String,
|
||||
isInitialPresentation: Boolean, filenameConverted: String): PresentationVO = {
|
||||
defaultPresentation: Boolean, filenameConverted: String): PresentationVO = {
|
||||
val pages = pres.pages.values.map { page =>
|
||||
PageVO(
|
||||
id = page.id,
|
||||
@ -92,7 +93,7 @@ object PresentationPodsApp {
|
||||
)
|
||||
}
|
||||
PresentationVO(pres.id, temporaryPresentationId, pres.name, pres.current, pages.toVector, pres.downloadable,
|
||||
pres.removable, isInitialPresentation, filenameConverted)
|
||||
pres.removable, defaultPresentation, filenameConverted)
|
||||
}
|
||||
|
||||
def setCurrentPresentationInPod(state: MeetingState2x, podId: String, nextCurrentPresId: String): Option[PresentationPod] = {
|
||||
@ -107,5 +108,11 @@ object PresentationPodsApp {
|
||||
def generateToken(podId: String, userId: String): String = {
|
||||
"PresUploadToken-" + RandomStringGenerator.randomAlphanumericString(8) + podId + "-" + userId
|
||||
}
|
||||
|
||||
def generatePresentationId(presFilename: String) = {
|
||||
val timestamp = System.currentTimeMillis
|
||||
DigestUtils.sha1Hex(presFilename + RandomStringGenerator.randomAlphanumericString(8)) + "-" + timestamp
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -1,11 +1,14 @@
|
||||
package org.bigbluebutton.core.apps.presentationpod
|
||||
|
||||
import org.apache.commons.codec.digest.DigestUtils
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.bus.MessageBus
|
||||
import org.bigbluebutton.core.domain.MeetingState2x
|
||||
import org.bigbluebutton.core.models.Users2x
|
||||
import org.bigbluebutton.core.running.LiveMeeting
|
||||
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
|
||||
import org.bigbluebutton.core.db.PresPresentationDAO
|
||||
import org.bigbluebutton.core.util.RandomStringGenerator
|
||||
|
||||
trait PresentationUploadTokenReqMsgHdlr extends RightsManagementTrait {
|
||||
this: PresentationPodHdlrs =>
|
||||
@ -13,13 +16,13 @@ trait PresentationUploadTokenReqMsgHdlr extends RightsManagementTrait {
|
||||
def handle(msg: PresentationUploadTokenReqMsg, state: MeetingState2x,
|
||||
liveMeeting: LiveMeeting, bus: MessageBus): MeetingState2x = {
|
||||
|
||||
def broadcastPresentationUploadTokenPassResp(msg: PresentationUploadTokenReqMsg, token: String): Unit = {
|
||||
def broadcastPresentationUploadTokenPassResp(msg: PresentationUploadTokenReqMsg, token: String, presId: String): Unit = {
|
||||
// send back to client
|
||||
val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, liveMeeting.props.meetingProp.intId, msg.header.userId)
|
||||
val envelope = BbbCoreEnvelope(PresentationUploadTokenPassRespMsg.NAME, routing)
|
||||
val header = BbbClientMsgHeader(PresentationUploadTokenPassRespMsg.NAME, liveMeeting.props.meetingProp.intId, msg.header.userId)
|
||||
|
||||
val body = PresentationUploadTokenPassRespMsgBody(msg.body.podId, token, msg.body.filename, msg.body.temporaryPresentationId)
|
||||
val body = PresentationUploadTokenPassRespMsgBody(msg.body.podId, token, msg.body.filename, msg.body.uploadTemporaryId, presId)
|
||||
val event = PresentationUploadTokenPassRespMsg(header, body)
|
||||
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
|
||||
bus.outGW.send(msgEvent)
|
||||
@ -37,13 +40,13 @@ trait PresentationUploadTokenReqMsgHdlr extends RightsManagementTrait {
|
||||
bus.outGW.send(msgEvent)
|
||||
}
|
||||
|
||||
def broadcastPresentationUploadTokenSysPubMsg(msg: PresentationUploadTokenReqMsg, token: String): Unit = {
|
||||
def broadcastPresentationUploadTokenSysPubMsg(msg: PresentationUploadTokenReqMsg, token: String, presId: String): Unit = {
|
||||
// send to bbb-web
|
||||
val routing = collection.immutable.HashMap("sender" -> "bbb-apps-akka")
|
||||
val envelope = BbbCoreEnvelope(PresentationUploadTokenSysPubMsg.NAME, routing)
|
||||
val header = BbbClientMsgHeader(PresentationUploadTokenSysPubMsg.NAME, liveMeeting.props.meetingProp.intId, msg.header.userId)
|
||||
|
||||
val body = PresentationUploadTokenSysPubMsgBody(msg.body.podId, token, msg.body.filename, liveMeeting.props.meetingProp.intId)
|
||||
val body = PresentationUploadTokenSysPubMsgBody(msg.body.podId, token, msg.body.filename, liveMeeting.props.meetingProp.intId, presId)
|
||||
val event = PresentationUploadTokenSysPubMsg(header, body)
|
||||
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
|
||||
|
||||
@ -68,9 +71,10 @@ trait PresentationUploadTokenReqMsgHdlr extends RightsManagementTrait {
|
||||
log.info("handlePresentationUploadTokenReqMsg" + liveMeeting.props.meetingProp.intId +
|
||||
" userId=" + msg.header.userId + " filename=" + msg.body.filename)
|
||||
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
|
||||
if (liveMeeting.props.meetingProp.disabledFeatures.contains("presentation")) {
|
||||
broadcastPresentationUploadTokenFailResp(msg)
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
val reason = "Presentation is disabled for this meeting"
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting)
|
||||
} else if (filterPresentationMessage(liveMeeting.users2x, msg.header.userId) &&
|
||||
@ -81,8 +85,19 @@ trait PresentationUploadTokenReqMsgHdlr extends RightsManagementTrait {
|
||||
} else {
|
||||
if (userIsAllowedToUploadInPod(msg.body.podId, msg.header.userId)) {
|
||||
val token = PresentationPodsApp.generateToken(msg.body.podId, msg.header.userId)
|
||||
broadcastPresentationUploadTokenPassResp(msg, token)
|
||||
broadcastPresentationUploadTokenSysPubMsg(msg, token)
|
||||
val presentationId = PresentationPodsApp.generatePresentationId(msg.body.filename)
|
||||
broadcastPresentationUploadTokenPassResp(msg, token, presentationId)
|
||||
broadcastPresentationUploadTokenSysPubMsg(msg, token, presentationId)
|
||||
|
||||
PresPresentationDAO.insertToken(
|
||||
meetingId,
|
||||
msg.header.userId,
|
||||
msg.body.uploadTemporaryId,
|
||||
presentationId,
|
||||
token,
|
||||
msg.body.filename
|
||||
)
|
||||
|
||||
} else {
|
||||
broadcastPresentationUploadTokenFailResp(msg)
|
||||
}
|
||||
|
@ -42,21 +42,20 @@ trait PresentationUploadedFileTimeoutErrorPubMsgHdlr {
|
||||
pod <- PresentationPodsApp.getPresentationPod(state, msg.body.podId)
|
||||
pres <- pod.getPresentation(msg.body.presentationId)
|
||||
} yield {
|
||||
val presWithError = PresentationInPod(pres.id, pres.name, pres.current, pres.pages, pres.downloadable, pres.removable,
|
||||
pres.filenameConverted, pres.uploadCompleted, pres.numPages, msg.body.messageKey, errorDetails)
|
||||
val presWithError = PresentationInPod(pres.id, pres.name, pres.default, pres.current, pres.pages, pres.downloadable,
|
||||
"", pres.removable, pres.filenameConverted, pres.uploadCompleted, pres.numPages, msg.body.messageKey, errorDetails)
|
||||
var pods = state.presentationPodManager.addPod(pod)
|
||||
pods = pods.addPresentationToPod(pod.id, presWithError)
|
||||
PresPresentationDAO.insertOrUpdate(msg.header.meetingId, presWithError)
|
||||
|
||||
state.update(pods)
|
||||
}
|
||||
|
||||
PresPresentationDAO.updateErrors(msg.body.presentationId, msg.body.messageKey, errorDetails)
|
||||
broadcastEvent(msg)
|
||||
|
||||
newState match {
|
||||
case Some(ns) => ns
|
||||
case None =>
|
||||
PresPresentationDAO.updateErrors(msg.body.presentationId, msg.body.messageKey, errorDetails)
|
||||
state
|
||||
case None => state
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package org.bigbluebutton.core.apps.presentationpod
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
|
||||
import org.bigbluebutton.core.bus.MessageBus
|
||||
import org.bigbluebutton.core.db.PresPresentationDAO
|
||||
import org.bigbluebutton.core.domain.MeetingState2x
|
||||
import org.bigbluebutton.core.running.LiveMeeting
|
||||
|
||||
@ -38,6 +39,8 @@ trait RemovePresentationPubMsgHdlr extends RightsManagementTrait {
|
||||
val podId = msg.body.podId
|
||||
val presentationId = msg.body.presentationId
|
||||
|
||||
PresPresentationDAO.delete(presentationId)
|
||||
|
||||
val newState = for {
|
||||
pod <- PresentationPodsApp.getPresentationPod(state, podId)
|
||||
} yield {
|
||||
|
@ -3,6 +3,7 @@ package org.bigbluebutton.core.apps.presentationpod
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
|
||||
import org.bigbluebutton.core.bus.MessageBus
|
||||
import org.bigbluebutton.core.db.PresPresentationDAO
|
||||
import org.bigbluebutton.core.domain.MeetingState2x
|
||||
import org.bigbluebutton.core.running.LiveMeeting
|
||||
|
||||
@ -17,8 +18,21 @@ trait SetPresentationDownloadablePubMsgHdlr extends RightsManagementTrait {
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
|
||||
if (filterPresentationMessage(liveMeeting.users2x, msg.header.userId) &&
|
||||
permissionFailed(PermissionCheck.GUEST_LEVEL, PermissionCheck.PRESENTER_LEVEL, liveMeeting.users2x, msg.header.userId)) {
|
||||
val reason = "No permission to remove presentation from meeting."
|
||||
permissionFailed(
|
||||
PermissionCheck.GUEST_LEVEL,
|
||||
PermissionCheck.PRESENTER_LEVEL, liveMeeting.users2x, msg.header.userId
|
||||
)) {
|
||||
val reason = "No permission to make presentation downloadable for meeting."
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting)
|
||||
state
|
||||
} else if (liveMeeting.props.meetingProp.disabledFeatures.contains("downloadPresentationOriginalFile")
|
||||
&& msg.body.fileStateType == "Original") {
|
||||
val reason = "Download original presentation is disabled for meeting."
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting)
|
||||
state
|
||||
} else if (liveMeeting.props.meetingProp.disabledFeatures.contains("downloadPresentationConvertedToPdf")
|
||||
&& msg.body.fileStateType == "Converted") {
|
||||
val reason = "Download converted presentation is disabled for meeting."
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting)
|
||||
state
|
||||
} else {
|
||||
@ -37,7 +51,9 @@ trait SetPresentationDownloadablePubMsgHdlr extends RightsManagementTrait {
|
||||
PresentationSender.broadcastSetPresentationDownloadableEvtMsg(bus, meetingId, pod.id,
|
||||
msg.header.userId, presentationId, downloadable, pres.name, downloadableExtension)
|
||||
|
||||
val pods = state.presentationPodManager.setPresentationDownloadableInPod(pod.id, presentationId, downloadable)
|
||||
val pods = state.presentationPodManager.setPresentationDownloadableInPod(pod.id, presentationId, downloadable, downloadableExtension)
|
||||
|
||||
PresPresentationDAO.updateDownloadable(presentationId, downloadable, downloadableExtension)
|
||||
state.update(pods)
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -34,6 +34,7 @@ trait SwitchTimerReqMsgHdlr extends RightsManagementTrait {
|
||||
} else {
|
||||
if (TimerModel.getStopwatch(liveMeeting.timerModel) != msg.body.stopwatch) {
|
||||
TimerModel.setStopwatch(liveMeeting.timerModel, msg.body.stopwatch)
|
||||
TimerModel.reset(liveMeeting.timerModel) //Reset on switch Stopwatch/Timer
|
||||
if (msg.body.stopwatch) {
|
||||
TimerModel.setTrack(liveMeeting.timerModel, "noTrack")
|
||||
}
|
||||
|
@ -2,8 +2,11 @@ package org.bigbluebutton.core.apps.users
|
||||
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.apps.RightsManagementTrait
|
||||
import org.bigbluebutton.core.models.{ UserState, Users2x }
|
||||
import org.bigbluebutton.core.apps.groupchats.GroupChatApp
|
||||
import org.bigbluebutton.core.db.ChatMessageDAO
|
||||
import org.bigbluebutton.core.models.{ GroupChatFactory, GroupChatMessage, Roles, UserState, Users2x }
|
||||
import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter }
|
||||
import org.bigbluebutton.core2.MeetingStatus2x
|
||||
import org.bigbluebutton.core2.message.senders.MsgBuilder
|
||||
|
||||
trait ChangeUserAwayReqMsgHdlr extends RightsManagementTrait {
|
||||
@ -30,6 +33,8 @@ trait ChangeUserAwayReqMsgHdlr extends RightsManagementTrait {
|
||||
outGW.send(msgEventChange)
|
||||
}
|
||||
|
||||
val permissions = MeetingStatus2x.getPermissions(liveMeeting.status)
|
||||
|
||||
for {
|
||||
user <- Users2x.findWithIntId(liveMeeting.users2x, msg.body.userId)
|
||||
newUserState <- Users2x.setUserAway(liveMeeting.users2x, user.intId, msg.body.away)
|
||||
@ -44,6 +49,14 @@ trait ChangeUserAwayReqMsgHdlr extends RightsManagementTrait {
|
||||
outGW.send(MsgBuilder.buildUserEmojiChangedEvtMsg(liveMeeting.props.meetingProp.intId, msg.body.userId, "none"))
|
||||
}
|
||||
|
||||
val msgMeta = Map(
|
||||
"away" -> msg.body.away
|
||||
)
|
||||
|
||||
if (!(user.role == Roles.VIEWER_ROLE && user.locked && permissions.disablePubChat) && ((user.away && !msg.body.away) || (!user.away && msg.body.away))) {
|
||||
ChatMessageDAO.insertSystemMsg(liveMeeting.props.meetingProp.intId, GroupChatApp.MAIN_PUBLIC_CHAT, "", GroupChatMessageType.USER_AWAY_STATUS_MSG, msgMeta, user.name)
|
||||
}
|
||||
|
||||
broadcast(newUserState, msg.body.away)
|
||||
}
|
||||
}
|
||||
|
@ -60,7 +60,8 @@ trait RegisterUserReqMsgHdlr {
|
||||
|
||||
val regUser = RegisteredUsers.create(msg.body.intUserId, msg.body.extUserId,
|
||||
msg.body.name, msg.body.role, msg.body.authToken, msg.body.sessionToken,
|
||||
msg.body.avatarURL, ColorPicker.nextColor(liveMeeting.props.meetingProp.intId), msg.body.guest, msg.body.authed, guestStatus, msg.body.excludeFromDashboard, msg.body.customParameters, false)
|
||||
msg.body.avatarURL, ColorPicker.nextColor(liveMeeting.props.meetingProp.intId), msg.body.guest, msg.body.authed,
|
||||
guestStatus, msg.body.excludeFromDashboard, msg.body.enforceLayout, msg.body.customParameters, false)
|
||||
|
||||
checkUserConcurrentAccesses(regUser)
|
||||
RegisteredUsers.add(liveMeeting.registeredUsers, regUser, liveMeeting.props.meetingProp.intId)
|
||||
|
@ -2,15 +2,14 @@ package org.bigbluebutton.core.apps.users
|
||||
|
||||
import org.bigbluebutton.common2.msgs.UserJoinMeetingReqMsg
|
||||
import org.bigbluebutton.core.apps.breakout.BreakoutHdlrHelpers
|
||||
import org.bigbluebutton.core.db.{ UserStateDAO }
|
||||
import org.bigbluebutton.core.db.{ UserDAO, UserStateDAO }
|
||||
import org.bigbluebutton.core.domain.MeetingState2x
|
||||
import org.bigbluebutton.core.models.{ RegisteredUser, RegisteredUsers, Users2x, VoiceUsers }
|
||||
import org.bigbluebutton.core.running.{ HandlerHelpers, LiveMeeting, MeetingActor, OutMsgRouter }
|
||||
import org.bigbluebutton.core2.message.senders.MsgBuilder
|
||||
import org.bigbluebutton.core.models._
|
||||
import org.bigbluebutton.core.running._
|
||||
import org.bigbluebutton.core2.message.senders._
|
||||
|
||||
trait UserJoinMeetingReqMsgHdlr extends HandlerHelpers {
|
||||
this: MeetingActor =>
|
||||
|
||||
val liveMeeting: LiveMeeting
|
||||
val outGW: OutMsgRouter
|
||||
|
||||
@ -18,41 +17,116 @@ trait UserJoinMeetingReqMsgHdlr extends HandlerHelpers {
|
||||
log.info("Received user joined meeting. user {} meetingId={}", msg.body.userId, msg.header.meetingId)
|
||||
|
||||
Users2x.findWithIntId(liveMeeting.users2x, msg.body.userId) match {
|
||||
case Some(reconnectingUser) =>
|
||||
if (reconnectingUser.userLeftFlag.left) {
|
||||
case Some(user) => handleUserReconnecting(user, msg, state)
|
||||
case None => handleUserJoining(msg, state)
|
||||
}
|
||||
}
|
||||
|
||||
private def handleUserJoining(msg: UserJoinMeetingReqMsg, state: MeetingState2x): MeetingState2x = {
|
||||
|
||||
val regUser = RegisteredUsers.getRegisteredUserWithToken(msg.body.authToken, msg.body.userId, liveMeeting.registeredUsers)
|
||||
log.info(s"Number of registered users [${RegisteredUsers.numRegisteredUsers(liveMeeting.registeredUsers)}]")
|
||||
|
||||
regUser.fold {
|
||||
handleFailedUserJoin(msg, "Invalid auth token.", EjectReasonCode.VALIDATE_TOKEN)
|
||||
state
|
||||
} { user =>
|
||||
val validationResult = for {
|
||||
_ <- checkIfUserGuestStatusIsAllowed(user)
|
||||
_ <- checkIfUserIsBanned(user)
|
||||
_ <- checkIfUserLoggedOut(user)
|
||||
_ <- validateMaxParticipants(user)
|
||||
} yield user
|
||||
|
||||
validationResult.fold(
|
||||
reason => handleFailedUserJoin(msg, reason._1, reason._2),
|
||||
validUser => handleSuccessfulUserJoin(msg, validUser)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private def handleSuccessfulUserJoin(msg: UserJoinMeetingReqMsg, regUser: RegisteredUser) = {
|
||||
val newState = userJoinMeeting(outGW, msg.body.authToken, msg.body.clientType, liveMeeting, state)
|
||||
updateParentMeetingWithNewListOfUsers()
|
||||
notifyPreviousUsersWithSameExtId(regUser)
|
||||
clearCachedVoiceUser(regUser)
|
||||
clearExpiredUserState(regUser)
|
||||
invalidateUserGraphqlConnection(regUser)
|
||||
|
||||
newState
|
||||
}
|
||||
|
||||
private def handleFailedUserJoin(msg: UserJoinMeetingReqMsg, failReason: String, failReasonCode: String) = {
|
||||
log.info("Ignoring user {} attempt to join in meeting {}. Reason Code: {}, Reason Message: {}", msg.body.userId, msg.header.meetingId, failReasonCode, failReason)
|
||||
UserDAO.updateJoinError(msg.body.userId, failReasonCode, failReason)
|
||||
state
|
||||
}
|
||||
|
||||
private def handleUserReconnecting(user: UserState, msg: UserJoinMeetingReqMsg, state: MeetingState2x): MeetingState2x = {
|
||||
if (user.userLeftFlag.left) {
|
||||
resetUserLeftFlag(msg)
|
||||
}
|
||||
state
|
||||
}
|
||||
|
||||
private def resetUserLeftFlag(msg: UserJoinMeetingReqMsg) = {
|
||||
log.info("Resetting flag that user left meeting. user {}", msg.body.userId)
|
||||
// User has reconnected. Just reset it's flag. ralam Oct 23, 2018
|
||||
sendUserLeftFlagUpdatedEvtMsg(outGW, liveMeeting, msg.body.userId, false)
|
||||
Users2x.resetUserLeftFlag(liveMeeting.users2x, msg.body.userId)
|
||||
}
|
||||
|
||||
state
|
||||
case None =>
|
||||
// Check if maxParticipants has been reached
|
||||
// User are able to reenter if he already joined previously with the same extId
|
||||
val userHasJoinedAlready = RegisteredUsers.findWithUserId(msg.body.userId, liveMeeting.registeredUsers) match {
|
||||
case Some(regUser: RegisteredUser) => RegisteredUsers.checkUserExtIdHasJoined(regUser.externId, liveMeeting.registeredUsers)
|
||||
case None => false
|
||||
private def validateMaxParticipants(regUser: RegisteredUser): Either[(String, String), Unit] = {
|
||||
val userHasJoinedAlready = RegisteredUsers.checkUserExtIdHasJoined(regUser.externId, liveMeeting.registeredUsers)
|
||||
val maxParticipants = liveMeeting.props.usersProp.maxUsers - 1
|
||||
|
||||
if (maxParticipants > 0 && //0 = no limit
|
||||
RegisteredUsers.numUniqueJoinedUsers(liveMeeting.registeredUsers) >= maxParticipants &&
|
||||
!userHasJoinedAlready) {
|
||||
Left(("The maximum number of participants allowed for this meeting has been reached.", EjectReasonCode.MAX_PARTICIPANTS))
|
||||
} else {
|
||||
Right(())
|
||||
}
|
||||
}
|
||||
val hasReachedMaxParticipants = liveMeeting.props.usersProp.maxUsers > 0 &&
|
||||
RegisteredUsers.numUniqueJoinedUsers(liveMeeting.registeredUsers) >= liveMeeting.props.usersProp.maxUsers &&
|
||||
userHasJoinedAlready == false
|
||||
|
||||
if (!hasReachedMaxParticipants) {
|
||||
val newState = userJoinMeeting(outGW, msg.body.authToken, msg.body.clientType, liveMeeting, state)
|
||||
private def checkIfUserGuestStatusIsAllowed(user: RegisteredUser): Either[(String, String), Unit] = {
|
||||
if (user.guestStatus != GuestStatus.ALLOW) {
|
||||
Left(("User is not allowed to join", EjectReasonCode.PERMISSION_FAILED))
|
||||
} else {
|
||||
Right(())
|
||||
}
|
||||
}
|
||||
|
||||
private def checkIfUserIsBanned(user: RegisteredUser): Either[(String, String), Unit] = {
|
||||
if (user.banned) {
|
||||
Left(("Banned user rejoining", EjectReasonCode.BANNED_USER_REJOINING))
|
||||
} else {
|
||||
Right(())
|
||||
}
|
||||
}
|
||||
|
||||
private def checkIfUserLoggedOut(user: RegisteredUser): Either[(String, String), Unit] = {
|
||||
if (user.loggedOut) {
|
||||
Left(("User had logged out", EjectReasonCode.USER_LOGGED_OUT))
|
||||
} else {
|
||||
Right(())
|
||||
}
|
||||
}
|
||||
|
||||
private def updateParentMeetingWithNewListOfUsers() = {
|
||||
if (liveMeeting.props.meetingProp.isBreakout) {
|
||||
BreakoutHdlrHelpers.updateParentMeetingWithUsers(liveMeeting, eventBus)
|
||||
}
|
||||
}
|
||||
|
||||
// Warn previous users that someone connected with same Id
|
||||
for {
|
||||
regUser <- RegisteredUsers.getRegisteredUserWithToken(msg.body.authToken, msg.body.userId,
|
||||
liveMeeting.registeredUsers)
|
||||
} yield {
|
||||
private def notifyPreviousUsersWithSameExtId(regUser: RegisteredUser) = {
|
||||
RegisteredUsers.findAllWithExternUserId(regUser.externId, liveMeeting.registeredUsers)
|
||||
.filter(u => u.id != regUser.id)
|
||||
.filter(_.id != regUser.id)
|
||||
.foreach { previousUser =>
|
||||
sendUserConnectedNotification(previousUser, regUser, liveMeeting)
|
||||
}
|
||||
}
|
||||
|
||||
private def sendUserConnectedNotification(previousUser: RegisteredUser, newUser: RegisteredUser, liveMeeting: LiveMeeting) = {
|
||||
val notifyUserEvent = MsgBuilder.buildNotifyUserInMeetingEvtMsg(
|
||||
previousUser.id,
|
||||
liveMeeting.props.meetingProp.intId,
|
||||
@ -60,22 +134,19 @@ trait UserJoinMeetingReqMsgHdlr extends HandlerHelpers {
|
||||
"promote",
|
||||
"app.mobileAppModal.userConnectedWithSameId",
|
||||
"Notification to warn that user connect again from other browser/device",
|
||||
Vector(regUser.name)
|
||||
Vector(newUser.name)
|
||||
)
|
||||
outGW.send(notifyUserEvent)
|
||||
}
|
||||
}
|
||||
|
||||
private def clearCachedVoiceUser(regUser: RegisteredUser) =
|
||||
// fresh user joined (not due to reconnection). Clear (pop) the cached voice user
|
||||
VoiceUsers.recoverVoiceUser(liveMeeting.voiceUsers, msg.body.userId)
|
||||
UserStateDAO.updateExpired(msg.body.userId, false)
|
||||
VoiceUsers.recoverVoiceUser(liveMeeting.voiceUsers, regUser.id)
|
||||
|
||||
private def clearExpiredUserState(regUser: RegisteredUser) =
|
||||
UserStateDAO.updateExpired(regUser.id, false)
|
||||
|
||||
private def invalidateUserGraphqlConnection(regUser: RegisteredUser) =
|
||||
Sender.sendInvalidateUserGraphqlConnectionSysMsg(liveMeeting.props.meetingProp.intId, regUser.id, regUser.sessionToken, "user_joined", outGW)
|
||||
|
||||
newState
|
||||
} else {
|
||||
log.info("Ignoring user {} attempt to join, once the meeting {} has reached max participants: {}", msg.body.userId, msg.header.meetingId, liveMeeting.props.usersProp.maxUsers)
|
||||
state
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,7 @@ import org.bigbluebutton.common2.msgs.UserLeaveReqMsg
|
||||
import org.bigbluebutton.core.domain.MeetingState2x
|
||||
import org.bigbluebutton.core.models.{ RegisteredUsers, Users2x }
|
||||
import org.bigbluebutton.core.running.{ HandlerHelpers, MeetingActor, OutMsgRouter }
|
||||
import org.bigbluebutton.core2.message.senders.Sender
|
||||
|
||||
trait UserLeaveReqMsgHdlr extends HandlerHelpers {
|
||||
this: MeetingActor =>
|
||||
@ -30,6 +31,7 @@ trait UserLeaveReqMsgHdlr extends HandlerHelpers {
|
||||
ru <- RegisteredUsers.findWithUserId(msg.body.userId, liveMeeting.registeredUsers)
|
||||
} yield {
|
||||
RegisteredUsers.setUserLoggedOutFlag(liveMeeting.registeredUsers, ru)
|
||||
Sender.sendInvalidateUserGraphqlConnectionSysMsg(liveMeeting.props.meetingProp.intId, ru.id, ru.sessionToken, "user_loggedout", outGW)
|
||||
}
|
||||
}
|
||||
state
|
||||
|
@ -2,12 +2,14 @@ package org.bigbluebutton.core.apps.users
|
||||
|
||||
import org.apache.pekko.actor.ActorContext
|
||||
import org.apache.pekko.event.Logging
|
||||
import org.bigbluebutton.Boot.eventBus
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.api.{SetPresenterInDefaultPodInternalMsg}
|
||||
import org.bigbluebutton.core.apps.ExternalVideoModel
|
||||
import org.bigbluebutton.core.bus.InternalEventBus
|
||||
import org.bigbluebutton.core.bus.{BigBlueButtonEvent, InternalEventBus}
|
||||
import org.bigbluebutton.core.models._
|
||||
import org.bigbluebutton.core.running.{LiveMeeting, OutMsgRouter}
|
||||
import org.bigbluebutton.core2.message.senders.{MsgBuilder, Sender}
|
||||
import org.bigbluebutton.core2.message.senders.{MsgBuilder}
|
||||
import org.bigbluebutton.core.apps.screenshare.ScreenshareApp2x
|
||||
import org.bigbluebutton.core.db.UserStateDAO
|
||||
|
||||
@ -67,8 +69,8 @@ object UsersApp {
|
||||
moderator <- Users2x.findModerator(liveMeeting.users2x)
|
||||
newPresenter <- Users2x.makePresenter(liveMeeting.users2x, moderator.intId)
|
||||
} yield {
|
||||
// println(s"automaticallyAssignPresenter: moderator=${moderator} newPresenter=${newPresenter.intId}");
|
||||
sendPresenterAssigned(outGW, meetingId, newPresenter.intId, newPresenter.name, newPresenter.intId)
|
||||
sendPresenterInPodReq(meetingId, newPresenter.intId)
|
||||
}
|
||||
}
|
||||
|
||||
@ -77,6 +79,10 @@ object UsersApp {
|
||||
outGW.send(event)
|
||||
}
|
||||
|
||||
def sendPresenterInPodReq(meetingId: String, newPresenterIntId: String): Unit = {
|
||||
eventBus.publish(BigBlueButtonEvent(meetingId, SetPresenterInDefaultPodInternalMsg(newPresenterIntId)))
|
||||
}
|
||||
|
||||
def sendUserLeftMeetingToAllClients(outGW: OutMsgRouter, meetingId: String,
|
||||
userId: String, eject: Boolean = false, ejectedBy: String = "", reason: String = "", reasonCode: String = ""): Unit = {
|
||||
// send a user left event for the clients to update
|
||||
|
@ -2,6 +2,7 @@ package org.bigbluebutton.core.apps.users
|
||||
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.bus.InternalEventBus
|
||||
import org.bigbluebutton.core.db.UserDAO
|
||||
import org.bigbluebutton.core.domain.MeetingState2x
|
||||
import org.bigbluebutton.core.models._
|
||||
import org.bigbluebutton.core.running.{ HandlerHelpers, LiveMeeting, OutMsgRouter }
|
||||
@ -15,103 +16,79 @@ trait ValidateAuthTokenReqMsgHdlr extends HandlerHelpers {
|
||||
val eventBus: InternalEventBus
|
||||
|
||||
def handleValidateAuthTokenReqMsg(msg: ValidateAuthTokenReqMsg, state: MeetingState2x): MeetingState2x = {
|
||||
log.debug("RECEIVED ValidateAuthTokenReqMsg msg {}", msg)
|
||||
log.debug(s"Received ValidateAuthTokenReqMsg msg $msg")
|
||||
|
||||
var failReason = "Invalid auth token."
|
||||
var failReasonCode = EjectReasonCode.VALIDATE_TOKEN
|
||||
val regUser = RegisteredUsers.getRegisteredUserWithToken(msg.body.authToken, msg.body.userId, liveMeeting.registeredUsers)
|
||||
log.info(s"Number of registered users [${RegisteredUsers.numRegisteredUsers(liveMeeting.registeredUsers)}]")
|
||||
|
||||
log.info("Number of registered users [{}]", RegisteredUsers.numRegisteredUsers(liveMeeting.registeredUsers))
|
||||
val regUser = RegisteredUsers.getRegisteredUserWithToken(msg.body.authToken, msg.body.userId,
|
||||
liveMeeting.registeredUsers)
|
||||
regUser match {
|
||||
case Some(u) =>
|
||||
// Check if maxParticipants has been reached
|
||||
// User are able to reenter if he already joined previously with the same extId
|
||||
val hasReachedMaxParticipants = liveMeeting.props.usersProp.maxUsers > 0 &&
|
||||
regUser.fold {
|
||||
sendFailedValidateAuthTokenRespMsg(msg, "Invalid auth token.", EjectReasonCode.VALIDATE_TOKEN)
|
||||
} { user =>
|
||||
val validationResult = for {
|
||||
_ <- checkIfUserGuestStatusIsAllowed(user)
|
||||
_ <- checkIfUserIsBanned(user)
|
||||
_ <- checkIfUserLoggedOut(user)
|
||||
_ <- validateMaxParticipants(user)
|
||||
} yield user
|
||||
|
||||
validationResult.fold(
|
||||
reason => sendFailedValidateAuthTokenRespMsg(msg, reason._1, reason._2),
|
||||
validUser => sendSuccessfulValidateAuthTokenRespMsg(validUser)
|
||||
)
|
||||
}
|
||||
|
||||
state
|
||||
}
|
||||
|
||||
private def validateMaxParticipants(user: RegisteredUser): Either[(String, String), Unit] = {
|
||||
if (liveMeeting.props.usersProp.maxUsers > 0 &&
|
||||
RegisteredUsers.numUniqueJoinedUsers(liveMeeting.registeredUsers) >= liveMeeting.props.usersProp.maxUsers &&
|
||||
RegisteredUsers.checkUserExtIdHasJoined(u.externId, liveMeeting.registeredUsers) == false
|
||||
|
||||
// Check if banned user is rejoining.
|
||||
// Fail validation if ejected user is rejoining.
|
||||
// ralam april 21, 2020
|
||||
if (u.guestStatus == GuestStatus.ALLOW && !u.banned && !u.loggedOut && !hasReachedMaxParticipants) {
|
||||
userValidated(u, state)
|
||||
RegisteredUsers.checkUserExtIdHasJoined(user.externId, liveMeeting.registeredUsers) == false) {
|
||||
Left(("The maximum number of participants allowed for this meeting has been reached.", EjectReasonCode.MAX_PARTICIPANTS))
|
||||
} else {
|
||||
if (u.banned) {
|
||||
failReason = "Banned user rejoining"
|
||||
failReasonCode = EjectReasonCode.BANNED_USER_REJOINING
|
||||
} else if (u.loggedOut) {
|
||||
failReason = "User had logged out"
|
||||
failReasonCode = EjectReasonCode.USER_LOGGED_OUT
|
||||
} else if (hasReachedMaxParticipants) {
|
||||
failReason = "The maximum number of participants allowed for this meeting has been reached."
|
||||
failReasonCode = EjectReasonCode.MAX_PARTICIPANTS
|
||||
}
|
||||
validateTokenFailed(
|
||||
outGW,
|
||||
meetingId = liveMeeting.props.meetingProp.intId,
|
||||
userId = msg.header.userId,
|
||||
authToken = msg.body.authToken,
|
||||
valid = false,
|
||||
waitForApproval = false,
|
||||
failReason,
|
||||
failReasonCode,
|
||||
state
|
||||
)
|
||||
}
|
||||
|
||||
case None =>
|
||||
validateTokenFailed(
|
||||
outGW,
|
||||
meetingId = liveMeeting.props.meetingProp.intId,
|
||||
userId = msg.header.userId,
|
||||
authToken = msg.body.authToken,
|
||||
valid = false,
|
||||
waitForApproval = false,
|
||||
failReason,
|
||||
failReasonCode,
|
||||
state
|
||||
)
|
||||
|
||||
Right(())
|
||||
}
|
||||
}
|
||||
|
||||
def validateTokenFailed(
|
||||
outGW: OutMsgRouter,
|
||||
meetingId: String,
|
||||
userId: String,
|
||||
authToken: String,
|
||||
valid: Boolean,
|
||||
waitForApproval: Boolean,
|
||||
reason: String,
|
||||
reasonCode: String,
|
||||
state: MeetingState2x
|
||||
): MeetingState2x = {
|
||||
val event = MsgBuilder.buildValidateAuthTokenRespMsg(meetingId, userId, authToken, valid, waitForApproval, 0,
|
||||
0, reasonCode, reason)
|
||||
outGW.send(event)
|
||||
|
||||
// send a system message to force disconnection
|
||||
// Comment out as meteor will disconnect the client. Requested by Tiago (ralam apr 28, 2020)
|
||||
//Sender.sendDisconnectClientSysMsg(meetingId, userId, SystemUser.ID, reasonCode, outGW)
|
||||
|
||||
state
|
||||
private def checkIfUserGuestStatusIsAllowed(user: RegisteredUser): Either[(String, String), Unit] = {
|
||||
if (user.guestStatus != GuestStatus.ALLOW) {
|
||||
Left(("User is not allowed to join", EjectReasonCode.PERMISSION_FAILED))
|
||||
} else {
|
||||
Right(())
|
||||
}
|
||||
}
|
||||
|
||||
def sendValidateAuthTokenRespMsg(meetingId: String, userId: String, authToken: String,
|
||||
valid: Boolean, waitForApproval: Boolean, registeredOn: Long, authTokenValidatedOn: Long,
|
||||
reasonCode: String = EjectReasonCode.NOT_EJECT, reason: String = "User not ejected"): Unit = {
|
||||
val event = MsgBuilder.buildValidateAuthTokenRespMsg(meetingId, userId, authToken, valid, waitForApproval, registeredOn,
|
||||
authTokenValidatedOn, reasonCode, reason)
|
||||
private def checkIfUserIsBanned(user: RegisteredUser): Either[(String, String), Unit] = {
|
||||
if (user.banned) {
|
||||
Left(("Banned user rejoining", EjectReasonCode.BANNED_USER_REJOINING))
|
||||
} else {
|
||||
Right(())
|
||||
}
|
||||
}
|
||||
|
||||
private def checkIfUserLoggedOut(user: RegisteredUser): Either[(String, String), Unit] = {
|
||||
if (user.loggedOut) {
|
||||
Left(("User had logged out", EjectReasonCode.USER_LOGGED_OUT))
|
||||
} else {
|
||||
Right(())
|
||||
}
|
||||
}
|
||||
|
||||
private def sendFailedValidateAuthTokenRespMsg(msg: ValidateAuthTokenReqMsg, failReason: String, failReasonCode: String) = {
|
||||
UserDAO.updateJoinError(msg.body.userId, failReasonCode, failReason)
|
||||
|
||||
val event = MsgBuilder.buildValidateAuthTokenRespMsg(liveMeeting.props.meetingProp.intId, msg.header.userId, msg.body.authToken, false, false, 0,
|
||||
0, failReasonCode, failReason)
|
||||
outGW.send(event)
|
||||
}
|
||||
|
||||
def userValidated(user: RegisteredUser, state: MeetingState2x): MeetingState2x = {
|
||||
def sendSuccessfulValidateAuthTokenRespMsg(user: RegisteredUser) = {
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
val updatedUser = RegisteredUsers.updateUserLastAuthTokenValidated(liveMeeting.registeredUsers, user)
|
||||
|
||||
sendValidateAuthTokenRespMsg(meetingId, updatedUser.id, updatedUser.authToken, valid = true, waitForApproval = false, updatedUser.registeredOn, updatedUser.lastAuthTokenValidatedOn)
|
||||
state
|
||||
val event = MsgBuilder.buildValidateAuthTokenRespMsg(meetingId, updatedUser.id, updatedUser.authToken, true, false, updatedUser.registeredOn,
|
||||
updatedUser.lastAuthTokenValidatedOn, EjectReasonCode.NOT_EJECT, "User not ejected")
|
||||
outGW.send(event)
|
||||
}
|
||||
|
||||
def sendAllUsersInMeeting(requesterId: String): Unit = {
|
||||
|
@ -33,8 +33,8 @@ trait UserJoinedVoiceConfEvtMsgHdlr extends SystemConfiguration {
|
||||
|
||||
def registerUserInRegisteredUsers() = {
|
||||
val regUser = RegisteredUsers.create(msg.body.intId, msg.body.voiceUserId,
|
||||
msg.body.callerIdName, Roles.VIEWER_ROLE, "", "", "", userColor,
|
||||
true, true, GuestStatus.WAIT, true, Map(), false)
|
||||
msg.body.callerIdName, Roles.VIEWER_ROLE, msg.body.intId, "", "", userColor,
|
||||
true, true, GuestStatus.WAIT, true, "", Map(), false)
|
||||
RegisteredUsers.add(liveMeeting.registeredUsers, regUser, liveMeeting.props.meetingProp.intId)
|
||||
}
|
||||
|
||||
@ -57,7 +57,7 @@ trait UserJoinedVoiceConfEvtMsgHdlr extends SystemConfiguration {
|
||||
locked = MeetingStatus2x.getPermissions(liveMeeting.status).lockOnJoin,
|
||||
avatar = "",
|
||||
color = userColor,
|
||||
clientType = "",
|
||||
clientType = if (isDialInUser) "dial-in-user" else "",
|
||||
pickExempted = false,
|
||||
userLeftFlag = UserLeftFlag(false, 0)
|
||||
)
|
||||
|
@ -4,6 +4,7 @@ import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.models._
|
||||
import org.bigbluebutton.core.apps.users.UsersApp
|
||||
import org.bigbluebutton.core.apps.breakout.BreakoutHdlrHelpers
|
||||
import org.bigbluebutton.core.db.UserDAO
|
||||
import org.bigbluebutton.core.running.{ LiveMeeting, MeetingActor, OutMsgRouter }
|
||||
|
||||
trait UserLeftVoiceConfEvtMsgHdlr {
|
||||
@ -39,6 +40,7 @@ trait UserLeftVoiceConfEvtMsgHdlr {
|
||||
UsersApp.guestWaitingLeft(liveMeeting, user.intId, outGW)
|
||||
}
|
||||
Users2x.remove(liveMeeting.users2x, user.intId)
|
||||
UserDAO.delete(user.intId)
|
||||
VoiceApp.removeUserFromVoiceConf(liveMeeting, outGW, msg.body.voiceUserId)
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
package org.bigbluebutton.core.apps.voice
|
||||
|
||||
import org.apache.pekko.actor.{ ActorContext, ActorSystem, Cancellable }
|
||||
import org.apache.pekko.actor.{ActorContext, ActorSystem, Cancellable}
|
||||
import org.bigbluebutton.SystemConfiguration
|
||||
import org.bigbluebutton.LockSettingsUtil
|
||||
import org.bigbluebutton.core.apps.breakout.BreakoutHdlrHelpers
|
||||
@ -11,8 +11,10 @@ import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.running.{LiveMeeting, MeetingActor, OutMsgRouter}
|
||||
import org.bigbluebutton.core.models._
|
||||
import org.bigbluebutton.core.apps.users.UsersApp
|
||||
import org.bigbluebutton.core.db.{UserDAO, UserVoiceDAO}
|
||||
import org.bigbluebutton.core.util.ColorPicker
|
||||
import org.bigbluebutton.core.util.TimeUtil
|
||||
|
||||
import scala.collection.immutable.Map
|
||||
import scala.concurrent.duration._
|
||||
|
||||
@ -323,6 +325,8 @@ object VoiceApp extends SystemConfiguration {
|
||||
uuid
|
||||
)
|
||||
VoiceUsers.add(liveMeeting.voiceUsers, voiceUserState)
|
||||
UserVoiceDAO.update(voiceUserState)
|
||||
UserDAO.updateVoiceUserJoined(voiceUserState)
|
||||
|
||||
broadcastEvent(voiceUserState)
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
package org.bigbluebutton.core.apps.voice
|
||||
|
||||
import org.bigbluebutton.common2.msgs.{ BbbClientMsgHeader, BbbCommonEnvCoreMsg, BbbCoreEnvelope, MessageTypes, Routing, VoiceCallStateEvtMsg, VoiceCallStateEvtMsgBody, VoiceConfCallStateEvtMsg }
|
||||
import org.bigbluebutton.core.models.{ VoiceUserState, VoiceUsers }
|
||||
import org.bigbluebutton.core.db.{ UserVoiceConfStateDAO, UserVoiceDAO }
|
||||
import org.bigbluebutton.core.running.{ LiveMeeting, MeetingActor, OutMsgRouter }
|
||||
|
||||
trait VoiceConfCallStateEvtMsgHdlr {
|
||||
@ -38,5 +38,9 @@ trait VoiceConfCallStateEvtMsgHdlr {
|
||||
val event = VoiceCallStateEvtMsg(header, body)
|
||||
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
|
||||
outGW.send(msgEvent)
|
||||
|
||||
if (msg.body.userId.nonEmpty) {
|
||||
UserVoiceConfStateDAO.insertOrUpdate(msg.body.userId, msg.body.voiceConf, msg.body.callSession, msg.body.clientSession, msg.body.callState)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,8 +3,9 @@ package org.bigbluebutton.core.apps.webcam
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.apps.PermissionCheck
|
||||
import org.bigbluebutton.core.bus.MessageBus
|
||||
import org.bigbluebutton.core.db.MeetingUsersPoliciesDAO
|
||||
import org.bigbluebutton.core.running.LiveMeeting
|
||||
import org.bigbluebutton.core2.message.senders.{ MsgBuilder }
|
||||
import org.bigbluebutton.core2.message.senders.MsgBuilder
|
||||
|
||||
trait UpdateWebcamsOnlyForModeratorCmdMsgHdlr {
|
||||
this: WebcamApp2x =>
|
||||
@ -50,6 +51,8 @@ trait UpdateWebcamsOnlyForModeratorCmdMsgHdlr {
|
||||
case Some(value) => {
|
||||
log.info(s"Change webcams only for moderator status. meetingId=${meetingId} value=${value}")
|
||||
|
||||
MeetingUsersPoliciesDAO.updateWebcamsOnlyForModerator(meetingId, msg.body.webcamsOnlyForModerator)
|
||||
|
||||
if (value) {
|
||||
val notifyEvent = MsgBuilder.buildNotifyAllInMeetingEvtMsg(
|
||||
meetingId,
|
||||
|
@ -38,7 +38,16 @@ trait DeleteWhiteboardAnnotationsPubMsgHdlr extends RightsManagementTrait {
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting)
|
||||
}
|
||||
} else {
|
||||
val deletedAnnotations = deleteWhiteboardAnnotations(msg.body.whiteboardId, msg.header.userId, msg.body.annotationsIds, liveMeeting, isUserAmongPresenters, isUserModerator)
|
||||
|
||||
val annotationsIds = {
|
||||
if (msg.body.annotationsIds.size > 0) {
|
||||
msg.body.annotationsIds
|
||||
} else {
|
||||
getWhiteboardAnnotations(msg.body.whiteboardId, liveMeeting).map(a => a.id)
|
||||
}
|
||||
}
|
||||
|
||||
val deletedAnnotations = deleteWhiteboardAnnotations(msg.body.whiteboardId, msg.header.userId, annotationsIds, liveMeeting, isUserAmongPresenters, isUserModerator)
|
||||
if (!deletedAnnotations.isEmpty) {
|
||||
broadcastEvent(msg, deletedAnnotations)
|
||||
}
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
@ -175,10 +175,11 @@ object BreakoutRoomDAO {
|
||||
}
|
||||
}
|
||||
|
||||
def updateRoomsDuration(meetingId: String, newDurationInSeconds: Int) = {
|
||||
def updateRoomsDuration(parentMeetingId: String, newDurationInSeconds: Int) = {
|
||||
DatabaseConnection.db.run(
|
||||
TableQuery[BreakoutRoomDbTableDef]
|
||||
.filter(_.parentMeetingId === meetingId)
|
||||
.filter(_.parentMeetingId === parentMeetingId)
|
||||
.filter(_.endedAt.isEmpty)
|
||||
.map(u => u.durationInSeconds)
|
||||
.update(newDurationInSeconds)
|
||||
).onComplete {
|
||||
|
104
akka-bbb-apps/src/main/scala/org/bigbluebutton/core/db/CaptionDAO.scala
Executable file
104
akka-bbb-apps/src/main/scala/org/bigbluebutton/core/db/CaptionDAO.scala
Executable 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)
|
||||
}
|
||||
}
|
||||
}
|
@ -11,7 +11,7 @@ case class ChatMessageDbModel(
|
||||
chatId: String,
|
||||
meetingId: String,
|
||||
correlationId: String,
|
||||
createdTime: Long,
|
||||
createdAt: java.sql.Timestamp,
|
||||
chatEmphasizedText: Boolean,
|
||||
message: String,
|
||||
messageType: String,
|
||||
@ -26,7 +26,7 @@ class ChatMessageDbTableDef(tag: Tag) extends Table[ChatMessageDbModel](tag, Non
|
||||
val chatId = column[String]("chatId")
|
||||
val meetingId = column[String]("meetingId")
|
||||
val correlationId = column[String]("correlationId")
|
||||
val createdTime = column[Long]("createdTime")
|
||||
val createdAt = column[java.sql.Timestamp]("createdAt")
|
||||
val chatEmphasizedText = column[Boolean]("chatEmphasizedText")
|
||||
val message = column[String]("message")
|
||||
val messageType = column[String]("messageType")
|
||||
@ -37,7 +37,7 @@ class ChatMessageDbTableDef(tag: Tag) extends Table[ChatMessageDbModel](tag, Non
|
||||
// val chat = foreignKey("chat_message_chat_fk", (chatId, meetingId), ChatTable.chats)(c => (c.chatId, c.meetingId), onDelete = ForeignKeyAction.Cascade)
|
||||
// val sender = foreignKey("chat_message_sender_fk", senderId, UserTable.users)(_.userId, onDelete = ForeignKeyAction.SetNull)
|
||||
|
||||
override def * = (messageId, chatId, meetingId, correlationId, createdTime, chatEmphasizedText, message, messageType, messageMetadata, senderId, senderName, senderRole) <> (ChatMessageDbModel.tupled, ChatMessageDbModel.unapply)
|
||||
override def * = (messageId, chatId, meetingId, correlationId, createdAt, chatEmphasizedText, message, messageType, messageMetadata, senderId, senderName, senderRole) <> (ChatMessageDbModel.tupled, ChatMessageDbModel.unapply)
|
||||
}
|
||||
|
||||
object ChatMessageDAO {
|
||||
@ -49,7 +49,7 @@ object ChatMessageDAO {
|
||||
chatId = chatId,
|
||||
meetingId = meetingId,
|
||||
correlationId = groupChatMessage.correlationId,
|
||||
createdTime = groupChatMessage.timestamp,
|
||||
createdAt = new java.sql.Timestamp(System.currentTimeMillis()),
|
||||
chatEmphasizedText = groupChatMessage.chatEmphasizedText,
|
||||
message = groupChatMessage.message,
|
||||
messageType = "default",
|
||||
@ -78,11 +78,11 @@ object ChatMessageDAO {
|
||||
chatId = chatId,
|
||||
meetingId = meetingId,
|
||||
correlationId = "",
|
||||
createdTime = System.currentTimeMillis(),
|
||||
createdAt = new java.sql.Timestamp(System.currentTimeMillis()),
|
||||
chatEmphasizedText = false,
|
||||
message = message,
|
||||
messageType = messageType,
|
||||
messageMetadata = Some(JsonUtils.mapToJson(messageMetadata)),
|
||||
messageMetadata = Some(JsonUtils.mapToJson(messageMetadata).compactPrint),
|
||||
senderId = None,
|
||||
senderName = senderName,
|
||||
senderRole = None
|
||||
|
@ -11,8 +11,9 @@ case class ChatUserDbModel(
|
||||
chatId: String,
|
||||
meetingId: String,
|
||||
userId: String,
|
||||
lastSeenAt: Long,
|
||||
typingAt: Option[java.sql.Timestamp],
|
||||
lastSeenAt: Option[java.sql.Timestamp],
|
||||
startedTypingAt: Option[java.sql.Timestamp],
|
||||
lastTypingAt: Option[java.sql.Timestamp],
|
||||
visible: Boolean
|
||||
)
|
||||
|
||||
@ -20,13 +21,14 @@ class ChatUserDbTableDef(tag: Tag) extends Table[ChatUserDbModel](tag, None, "ch
|
||||
val chatId = column[String]("chatId", O.PrimaryKey)
|
||||
val meetingId = column[String]("meetingId", O.PrimaryKey)
|
||||
val userId = column[String]("userId", O.PrimaryKey)
|
||||
val lastSeenAt = column[Long]("lastSeenAt")
|
||||
val typingAt = column[Option[java.sql.Timestamp]]("typingAt")
|
||||
val lastSeenAt = column[Option[java.sql.Timestamp]]("lastSeenAt")
|
||||
val startedTypingAt = column[Option[java.sql.Timestamp]]("startedTypingAt")
|
||||
val lastTypingAt = column[Option[java.sql.Timestamp]]("lastTypingAt")
|
||||
val visible = column[Boolean]("visible")
|
||||
// val chat = foreignKey("chat_message_chat_fk", (chatId, meetingId), ChatTable.chats)(c => (c.chatId, c.meetingId), onDelete = ForeignKeyAction.Cascade)
|
||||
// val sender = foreignKey("chat_message_sender_fk", senderId, UserTable.users)(_.userId, onDelete = ForeignKeyAction.SetNull)
|
||||
|
||||
override def * = (chatId, meetingId, userId, lastSeenAt, typingAt, visible) <> (ChatUserDbModel.tupled, ChatUserDbModel.unapply)
|
||||
override def * = (chatId, meetingId, userId, lastSeenAt, startedTypingAt, lastTypingAt, visible) <> (ChatUserDbModel.tupled, ChatUserDbModel.unapply)
|
||||
}
|
||||
|
||||
object ChatUserDAO {
|
||||
@ -46,8 +48,9 @@ object ChatUserDAO {
|
||||
userId = userId,
|
||||
chatId = chatId,
|
||||
meetingId = meetingId,
|
||||
lastSeenAt = 0,
|
||||
typingAt = None,
|
||||
lastSeenAt = None,
|
||||
startedTypingAt = None,
|
||||
lastTypingAt = None,
|
||||
visible = visible
|
||||
)
|
||||
)
|
||||
@ -63,11 +66,11 @@ object ChatUserDAO {
|
||||
.filter(_.meetingId === meetingId)
|
||||
.filter(_.chatId === (if (chatId == "public") "MAIN-PUBLIC-GROUP-CHAT" else chatId))
|
||||
.filter(_.userId === userId)
|
||||
.map(u => (u.typingAt))
|
||||
.map(u => (u.lastTypingAt))
|
||||
.update(Some(new java.sql.Timestamp(System.currentTimeMillis())))
|
||||
).onComplete {
|
||||
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated typingAt on chat_user table!")
|
||||
case Failure(e) => DatabaseConnection.logger.debug(s"Error updating typingAt on chat_user table: $e")
|
||||
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated lastTypingAt on chat_user table!")
|
||||
case Failure(e) => DatabaseConnection.logger.debug(s"Error updating lastTypingAt on chat_user table: $e")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -20,7 +20,7 @@ case class MeetingDbModel(
|
||||
presentationUploadExternalUrl: String,
|
||||
learningDashboardAccessToken: String,
|
||||
createdTime: Long,
|
||||
duration: Int
|
||||
durationInSeconds: Int
|
||||
)
|
||||
|
||||
class MeetingDbTableDef(tag: Tag) extends Table[MeetingDbModel](tag, None, "meeting") {
|
||||
@ -37,7 +37,7 @@ class MeetingDbTableDef(tag: Tag) extends Table[MeetingDbModel](tag, None, "meet
|
||||
presentationUploadExternalUrl,
|
||||
learningDashboardAccessToken,
|
||||
createdTime,
|
||||
duration
|
||||
durationInSeconds
|
||||
) <> (MeetingDbModel.tupled, MeetingDbModel.unapply)
|
||||
val meetingId = column[String]("meetingId", O.PrimaryKey)
|
||||
val extId = column[String]("extId")
|
||||
@ -51,11 +51,11 @@ class MeetingDbTableDef(tag: Tag) extends Table[MeetingDbModel](tag, None, "meet
|
||||
val presentationUploadExternalUrl = column[String]("presentationUploadExternalUrl")
|
||||
val learningDashboardAccessToken = column[String]("learningDashboardAccessToken")
|
||||
val createdTime = column[Long]("createdTime")
|
||||
val duration = column[Int]("duration")
|
||||
val durationInSeconds = column[Int]("durationInSeconds")
|
||||
}
|
||||
|
||||
object MeetingDAO {
|
||||
def insert(meetingProps: DefaultProps) = {
|
||||
def insert(meetingProps: DefaultProps, clientSettings: Map[String, Object]) = {
|
||||
DatabaseConnection.db.run(
|
||||
TableQuery[MeetingDbTableDef].forceInsert(
|
||||
MeetingDbModel(
|
||||
@ -71,28 +71,46 @@ object MeetingDAO {
|
||||
presentationUploadExternalUrl = meetingProps.meetingProp.presentationUploadExternalUrl,
|
||||
learningDashboardAccessToken = meetingProps.password.learningDashboardAccessToken,
|
||||
createdTime = meetingProps.durationProps.createdTime,
|
||||
duration = meetingProps.durationProps.duration
|
||||
durationInSeconds = meetingProps.durationProps.duration * 60
|
||||
)
|
||||
)
|
||||
).onComplete {
|
||||
case Success(rowsAffected) => {
|
||||
DatabaseConnection.logger.debug(s"$rowsAffected row(s) inserted in Meeting table!")
|
||||
ChatDAO.insert(meetingProps.meetingProp.intId, GroupChatApp.createDefaultPublicGroupChat())
|
||||
MeetingUsersPoliciesDAO.insert(meetingProps.meetingProp.intId, meetingProps.usersProp)
|
||||
MeetingLockSettingsDAO.insert(meetingProps.meetingProp.intId, meetingProps.lockSettingsProps)
|
||||
MeetingMetadataDAO.insert(meetingProps.meetingProp.intId, meetingProps.metadataProp)
|
||||
MeetingRecordingPoliciesDAO.insert(meetingProps.meetingProp.intId, meetingProps.recordProp)
|
||||
MeetingVoiceDAO.insert(meetingProps.meetingProp.intId, meetingProps.voiceProp)
|
||||
ChatDAO.insert(meetingProps.meetingProp.intId, GroupChatApp.createDefaultPublicGroupChat())
|
||||
MeetingWelcomeDAO.insert(meetingProps.meetingProp.intId, meetingProps.welcomeProp)
|
||||
MeetingGroupDAO.insert(meetingProps.meetingProp.intId, meetingProps.groups)
|
||||
MeetingBreakoutDAO.insert(meetingProps.meetingProp.intId, meetingProps.breakoutProps)
|
||||
TimerDAO.insert(meetingProps.meetingProp.intId)
|
||||
LayoutDAO.insert(meetingProps.meetingProp.intId, meetingProps.usersProp.meetingLayout)
|
||||
MeetingClientSettingsDAO.insert(meetingProps.meetingProp.intId, JsonUtils.mapToJson(clientSettings))
|
||||
}
|
||||
case Failure(e) => DatabaseConnection.logger.error(s"Error inserting Meeting: $e")
|
||||
}
|
||||
}
|
||||
|
||||
def updateMeetingDurationByParentMeeting(parentMeetingId: String, newDurationInSeconds: Int) = {
|
||||
val subqueryBreakoutRooms = TableQuery[BreakoutRoomDbTableDef]
|
||||
.filter(_.parentMeetingId === parentMeetingId)
|
||||
.filter(_.endedAt.isEmpty)
|
||||
.map(_.externalId)
|
||||
|
||||
DatabaseConnection.db.run(
|
||||
TableQuery[MeetingDbTableDef]
|
||||
.filter(_.extId in subqueryBreakoutRooms)
|
||||
.map(u => u.durationInSeconds)
|
||||
.update(newDurationInSeconds)
|
||||
).onComplete {
|
||||
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated durationInSeconds on Meeting table")
|
||||
case Failure(e) => DatabaseConnection.logger.debug(s"Error updating durationInSeconds on Meeting: $e")
|
||||
}
|
||||
}
|
||||
|
||||
def delete(meetingId: String) = {
|
||||
DatabaseConnection.db.run(
|
||||
TableQuery[MeetingDbTableDef]
|
||||
|
@ -18,7 +18,8 @@ case class MeetingLockSettingsDbModel(
|
||||
hideUserList: Boolean,
|
||||
lockOnJoin: Boolean,
|
||||
lockOnJoinConfigurable: Boolean,
|
||||
hideViewersCursor: Boolean
|
||||
hideViewersCursor: Boolean,
|
||||
hideViewersAnnotation: Boolean
|
||||
)
|
||||
|
||||
class MeetingLockSettingsDbTableDef(tag: Tag) extends Table[MeetingLockSettingsDbModel](tag, "meeting_lockSettings") {
|
||||
@ -32,10 +33,11 @@ class MeetingLockSettingsDbTableDef(tag: Tag) extends Table[MeetingLockSettingsD
|
||||
val lockOnJoin = column[Boolean]("lockOnJoin")
|
||||
val lockOnJoinConfigurable = column[Boolean]("lockOnJoinConfigurable")
|
||||
val hideViewersCursor = column[Boolean]("hideViewersCursor")
|
||||
val hideViewersAnnotation = column[Boolean]("hideViewersAnnotation")
|
||||
|
||||
// def fk_meetingId: ForeignKeyQuery[MeetingDbTableDef, MeetingDbModel] = foreignKey("fk_meetingId", meetingId, TableQuery[MeetingDbTableDef])(_.meetingId)
|
||||
|
||||
override def * : ProvenShape[MeetingLockSettingsDbModel] = (meetingId, disableCam, disableMic, disablePrivateChat, disablePublicChat, disableNotes, hideUserList, lockOnJoin, lockOnJoinConfigurable, hideViewersCursor) <> (MeetingLockSettingsDbModel.tupled, MeetingLockSettingsDbModel.unapply)
|
||||
override def * : ProvenShape[MeetingLockSettingsDbModel] = (meetingId, disableCam, disableMic, disablePrivateChat, disablePublicChat, disableNotes, hideUserList, lockOnJoin, lockOnJoinConfigurable, hideViewersCursor, hideViewersAnnotation) <> (MeetingLockSettingsDbModel.tupled, MeetingLockSettingsDbModel.unapply)
|
||||
}
|
||||
|
||||
object MeetingLockSettingsDAO {
|
||||
@ -52,7 +54,8 @@ object MeetingLockSettingsDAO {
|
||||
hideUserList = lockSettingsProps.hideUserList,
|
||||
lockOnJoin = lockSettingsProps.lockOnJoin,
|
||||
lockOnJoinConfigurable = lockSettingsProps.lockOnJoinConfigurable,
|
||||
hideViewersCursor = lockSettingsProps.hideViewersCursor
|
||||
hideViewersCursor = lockSettingsProps.hideViewersCursor,
|
||||
hideViewersAnnotation = lockSettingsProps.hideViewersAnnotation,
|
||||
)
|
||||
)
|
||||
).onComplete {
|
||||
@ -76,7 +79,8 @@ object MeetingLockSettingsDAO {
|
||||
hideUserList = permissions.hideUserList,
|
||||
lockOnJoin = permissions.lockOnJoin,
|
||||
lockOnJoinConfigurable = permissions.lockOnJoinConfigurable,
|
||||
hideViewersCursor = permissions.hideViewersCursor
|
||||
hideViewersCursor = permissions.hideViewersCursor,
|
||||
hideViewersAnnotation = permissions.hideViewersAnnotation,
|
||||
),
|
||||
)
|
||||
).onComplete {
|
||||
|
@ -73,8 +73,8 @@ object MeetingUsersPoliciesDAO {
|
||||
.map(u => u.guestPolicy)
|
||||
.update(policy.policy)
|
||||
).onComplete {
|
||||
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated on meeting_usersPolicies table!")
|
||||
case Failure(e) => DatabaseConnection.logger.error(s"Error updating meeting_usersPolicies: $e")
|
||||
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated guestPolicy on meeting_usersPolicies table!")
|
||||
case Failure(e) => DatabaseConnection.logger.error(s"Error updating guestPolicy on meeting_usersPolicies: $e")
|
||||
}
|
||||
}
|
||||
|
||||
@ -90,8 +90,20 @@ object MeetingUsersPoliciesDAO {
|
||||
}
|
||||
)
|
||||
).onComplete {
|
||||
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated on meeting_usersPolicies table!")
|
||||
case Failure(e) => DatabaseConnection.logger.error(s"Error updating meeting_usersPolicies: $e")
|
||||
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated guestLobbyMessage on meeting_usersPolicies table!")
|
||||
case Failure(e) => DatabaseConnection.logger.error(s"Error updating guestLobbyMessage on meeting_usersPolicies: $e")
|
||||
}
|
||||
}
|
||||
|
||||
def updateWebcamsOnlyForModerator(meetingId: String, webcamsOnlyForModerator: Boolean) = {
|
||||
DatabaseConnection.db.run(
|
||||
TableQuery[MeetingUsersPoliciesDbTableDef]
|
||||
.filter(_.meetingId === meetingId)
|
||||
.map(u => u.webcamsOnlyForModerator)
|
||||
.update(webcamsOnlyForModerator)
|
||||
).onComplete {
|
||||
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated webcamsOnlyForModerator on meeting_usersPolicies table!")
|
||||
case Failure(e) => DatabaseConnection.logger.error(s"Error updating webcamsOnlyForModerator on meeting_usersPolicies: $e")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
@ -1,12 +1,16 @@
|
||||
package org.bigbluebutton.core.db
|
||||
|
||||
import com.github.tminglei.slickpg._
|
||||
import spray.json.{ JsArray, JsBoolean, JsNumber, JsObject, JsString, JsValue, JsonWriter }
|
||||
import spray.json.{ _ }
|
||||
import org.apache.pekko.http.scaladsl.model.ParsingException
|
||||
import org.bigbluebutton.common2.domain.SimpleVoteOutVO
|
||||
import spray.json.{ JsArray, JsBoolean, JsNumber, JsObject, JsString, JsValue, JsonWriter, _ }
|
||||
|
||||
import scala.util.{ Failure, Success, Try }
|
||||
|
||||
trait PostgresProfile extends ExPostgresProfile
|
||||
with PgArraySupport {
|
||||
// def pgjson = "jsonb" // jsonb support is in postgres 9.4.0 onward; for 9.3.x use "json"
|
||||
with PgArraySupport
|
||||
with PgSprayJsonSupport {
|
||||
def pgjson = "jsonb" // jsonb support is in postgres 9.4.0 onward; for 9.3.x use "json"
|
||||
|
||||
// Add back `capabilities.insertOrUpdate` to enable native `upsert` support; for postgres 9.5+
|
||||
override protected def computeCapabilities: Set[slick.basic.Capability] =
|
||||
@ -14,7 +18,7 @@ trait PostgresProfile extends ExPostgresProfile
|
||||
|
||||
override val api = PgAPI
|
||||
|
||||
object PgAPI extends API with ArrayImplicits // with DateTimeImplicits
|
||||
object PgAPI extends API with ArrayImplicits with SprayJsonImplicits // with DateTimeImplicits
|
||||
{
|
||||
implicit val strListTypeMapper = new SimpleArrayJdbcType[String]("text").to(_.toList)
|
||||
}
|
||||
@ -34,6 +38,8 @@ object JsonUtils {
|
||||
case m: Map[_, _] => JsObject(m.asInstanceOf[Map[String, Any]].map { case (k, v) => k -> write(v) })
|
||||
case l: List[_] => JsArray(l.map(write).toVector)
|
||||
case a: Array[_] => JsArray(a.map(write).toVector)
|
||||
case v: SimpleVoteOutVO => JsObject("id" -> JsNumber(v.id), "key" -> JsString(v.key), "numVotes" -> JsNumber(v.numVotes))
|
||||
case null => JsNull
|
||||
case _ => throw new IllegalArgumentException(s"Unsupported type: ${x.getClass.getName}")
|
||||
// case _ => JsNull
|
||||
}
|
||||
@ -46,7 +52,16 @@ object JsonUtils {
|
||||
}
|
||||
|
||||
def mapToJson(genericMap: Map[String, Any]) = {
|
||||
genericMap.toJson.compactPrint
|
||||
genericMap.toJson
|
||||
}
|
||||
|
||||
def stringToJson(jsonString: String): JsValue = {
|
||||
Try(jsonString.parseJson) match {
|
||||
case Success(jsValue) => jsValue
|
||||
case Failure(exception) =>
|
||||
println(s"Failed to parse JSON string: $exception")
|
||||
"{}".parseJson
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -36,7 +36,7 @@ object PresAnnotationDAO {
|
||||
annotationId = annotation.id,
|
||||
pageId = annotation.wbId,
|
||||
userId = annotation.userId,
|
||||
annotationInfo = JsonUtils.mapToJson(annotation.annotationInfo),
|
||||
annotationInfo = JsonUtils.mapToJson(annotation.annotationInfo).compactPrint,
|
||||
lastHistorySequence = sequence.getOrElse(0),
|
||||
lastUpdatedAt = new java.sql.Timestamp(System.currentTimeMillis())
|
||||
)
|
||||
|
@ -34,7 +34,7 @@ object PresAnnotationHistoryDAO {
|
||||
annotationId = annotationDiff.id,
|
||||
pageId = annotationDiff.wbId,
|
||||
userId = annotationDiff.userId,
|
||||
annotationInfo = JsonUtils.mapToJson(annotationDiff.annotationInfo)
|
||||
annotationInfo = JsonUtils.mapToJson(annotationDiff.annotationInfo).compactPrint
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
package org.bigbluebutton.core.db
|
||||
|
||||
import org.bigbluebutton.core.models.{ PresentationInPod, PresentationPage }
|
||||
|
||||
import org.bigbluebutton.core.models.PresentationInPod
|
||||
import slick.jdbc.PostgresProfile.api._
|
||||
import PostgresProfile.api._
|
||||
import spray.json.JsValue
|
||||
import spray.json._
|
||||
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
import scala.util.{ Failure, Success }
|
||||
@ -12,7 +12,8 @@ case class PresPageDbModel(
|
||||
pageId: String,
|
||||
presentationId: String,
|
||||
num: Int,
|
||||
urls: String,
|
||||
urlsJson: JsValue,
|
||||
content: String,
|
||||
slideRevealed: Boolean,
|
||||
current: Boolean,
|
||||
xOffset: Double,
|
||||
@ -25,14 +26,15 @@ case class PresPageDbModel(
|
||||
viewBoxHeight: Double,
|
||||
maxImageWidth: Int,
|
||||
maxImageHeight: Int,
|
||||
converted: Boolean
|
||||
uploadCompleted: Boolean
|
||||
)
|
||||
|
||||
class PresPageDbTableDef(tag: Tag) extends Table[PresPageDbModel](tag, None, "pres_page") {
|
||||
val pageId = column[String]("pageId", O.PrimaryKey)
|
||||
val presentationId = column[String]("presentationId")
|
||||
val num = column[Int]("num")
|
||||
val urls = column[String]("urls")
|
||||
val urlsJson = column[JsValue]("urlsJson")
|
||||
val content = column[String]("content")
|
||||
val slideRevealed = column[Boolean]("slideRevealed")
|
||||
val current = column[Boolean]("current")
|
||||
val xOffset = column[Double]("xOffset")
|
||||
@ -45,12 +47,46 @@ class PresPageDbTableDef(tag: Tag) extends Table[PresPageDbModel](tag, None, "pr
|
||||
val viewBoxHeight = column[Double]("viewBoxHeight")
|
||||
val maxImageWidth = column[Int]("maxImageWidth")
|
||||
val maxImageHeight = column[Int]("maxImageHeight")
|
||||
val converted = column[Boolean]("converted")
|
||||
val uploadCompleted = column[Boolean]("uploadCompleted")
|
||||
// val presentation = foreignKey("presentation_fk", presentationId, Presentations)(_.presentationId, onDelete = ForeignKeyAction.Cascade)
|
||||
def * = (pageId, presentationId, num, urls, slideRevealed, current, xOffset, yOffset, widthRatio, heightRatio, width, height, viewBoxWidth, viewBoxHeight, maxImageWidth, maxImageHeight, converted) <> (PresPageDbModel.tupled, PresPageDbModel.unapply)
|
||||
def * = (pageId, presentationId, num, urlsJson, content, slideRevealed, current, xOffset, yOffset, widthRatio, heightRatio, width, height, viewBoxWidth, viewBoxHeight, maxImageWidth, maxImageHeight, uploadCompleted) <> (PresPageDbModel.tupled, PresPageDbModel.unapply)
|
||||
}
|
||||
|
||||
object PresPageDAO {
|
||||
implicit val mapFormat: JsonWriter[Map[String, String]] = new JsonWriter[Map[String, String]] {
|
||||
def write(m: Map[String, String]): JsValue = {
|
||||
JsObject(m.map { case (k, v) => k -> JsString(v) })
|
||||
}
|
||||
}
|
||||
def insert(presentationId: String, page: PresentationPage) = {
|
||||
DatabaseConnection.db.run(
|
||||
TableQuery[PresPageDbTableDef].insertOrUpdate(
|
||||
PresPageDbModel(
|
||||
pageId = page.id,
|
||||
presentationId = presentationId,
|
||||
num = page.num,
|
||||
urlsJson = page.urls.toJson,
|
||||
content = page.content,
|
||||
slideRevealed = page.current,
|
||||
current = page.current,
|
||||
xOffset = page.xOffset,
|
||||
yOffset = page.yOffset,
|
||||
widthRatio = page.widthRatio,
|
||||
heightRatio = page.heightRatio,
|
||||
width = page.width,
|
||||
height = page.height,
|
||||
viewBoxWidth = 1,
|
||||
viewBoxHeight = 1,
|
||||
maxImageWidth = 1440,
|
||||
maxImageHeight = 1080,
|
||||
uploadCompleted = page.converted
|
||||
)
|
||||
)
|
||||
).onComplete {
|
||||
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) inserted on PresentationPage table!")
|
||||
case Failure(e) => DatabaseConnection.logger.debug(s"Error inserting PresentationPage: $e")
|
||||
}
|
||||
}
|
||||
|
||||
def setCurrentPage(presentation: PresentationInPod, pageId: String) = {
|
||||
DatabaseConnection.db.run(
|
||||
|
@ -1,27 +1,70 @@
|
||||
package org.bigbluebutton.core.db
|
||||
|
||||
import slick.jdbc.PostgresProfile.api._
|
||||
import org.bigbluebutton.core.models.{ PresentationInPod }
|
||||
import PostgresProfile.api._
|
||||
import org.bigbluebutton.core.models.PresentationInPod
|
||||
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
import scala.util.{ Failure, Success }
|
||||
import scala.util.{Failure, Success}
|
||||
import spray.json._
|
||||
|
||||
case class PresPresentationDbModel(presentationId: String, meetingId: String, current: Boolean, downloadable: Boolean, removable: Boolean, uploadCompleted: Boolean, numPages: Int, errorMsgKey: String, errorDetails: String)
|
||||
import scala.concurrent.Future
|
||||
|
||||
|
||||
|
||||
case class PresPresentationDbModel(
|
||||
presentationId: String,
|
||||
meetingId: String,
|
||||
uploadUserId: Option[String],
|
||||
uploadTemporaryId: Option[String],
|
||||
uploadToken: Option[String],
|
||||
name: String,
|
||||
filenameConverted: String,
|
||||
isDefault: Boolean,
|
||||
current: Boolean,
|
||||
downloadable: Boolean,
|
||||
downloadFileExtension: Option[String],
|
||||
downloadFileUri: Option[String],
|
||||
removable: Boolean,
|
||||
uploadInProgress: Boolean,
|
||||
uploadCompleted: Boolean,
|
||||
uploadErrorMsgKey: Option[String],
|
||||
uploadErrorDetailsJson: Option[JsValue],
|
||||
totalPages: Int,
|
||||
exportToChatStatus: Option[String],
|
||||
exportToChatCurrentPage: Option[Int],
|
||||
exportToChatHasError: Option[Boolean]
|
||||
)
|
||||
|
||||
class PresPresentationDbTableDef(tag: Tag) extends Table[PresPresentationDbModel](tag, None, "pres_presentation") {
|
||||
val presentationId = column[String]("presentationId", O.PrimaryKey)
|
||||
val meetingId = column[String]("meetingId")
|
||||
val uploadUserId = column[Option[String]]("uploadUserId")
|
||||
val uploadTemporaryId = column[Option[String]]("uploadTemporaryId")
|
||||
val uploadToken = column[Option[String]]("uploadToken")
|
||||
val name = column[String]("name")
|
||||
val filenameConverted = column[String]("filenameConverted")
|
||||
val isDefault = column[Boolean]("isDefault")
|
||||
val current = column[Boolean]("current")
|
||||
val downloadable = column[Boolean]("downloadable")
|
||||
val downloadFileExtension = column[Option[String]]("downloadFileExtension")
|
||||
val downloadFileUri = column[Option[String]]("downloadFileUri")
|
||||
val removable = column[Boolean]("removable")
|
||||
val uploadInProgress = column[Boolean]("uploadInProgress")
|
||||
val uploadCompleted = column[Boolean]("uploadCompleted")
|
||||
val numPages = column[Int]("numPages")
|
||||
val errorMsgKey = column[String]("errorMsgKey")
|
||||
val errorDetails = column[String]("errorDetails")
|
||||
val uploadErrorMsgKey = column[Option[String]]("uploadErrorMsgKey")
|
||||
val uploadErrorDetailsJson = column[Option[JsValue]]("uploadErrorDetailsJson")
|
||||
val totalPages = column[Int]("totalPages")
|
||||
val exportToChatStatus = column[Option[String]]("exportToChatStatus")
|
||||
val exportToChatCurrentPage = column[Option[Int]]("exportToChatCurrentPage")
|
||||
val exportToChatHasError = column[Option[Boolean]]("exportToChatHasError")
|
||||
|
||||
// val meeting = foreignKey("meeting_fk", meetingId, Meetings)(_.meetingId, onDelete = ForeignKeyAction.Cascade)
|
||||
|
||||
def * = (presentationId, meetingId, current, downloadable, removable, uploadCompleted, numPages, errorMsgKey, errorDetails) <> (PresPresentationDbModel.tupled, PresPresentationDbModel.unapply)
|
||||
def * = (
|
||||
presentationId, meetingId, uploadUserId, uploadTemporaryId, uploadToken, name, filenameConverted, isDefault, current, downloadable, downloadFileExtension, downloadFileUri, removable,
|
||||
uploadInProgress, uploadCompleted, uploadErrorMsgKey, uploadErrorDetailsJson, totalPages,
|
||||
exportToChatStatus, exportToChatCurrentPage, exportToChatHasError
|
||||
) <> (PresPresentationDbModel.tupled, PresPresentationDbModel.unapply)
|
||||
}
|
||||
|
||||
object PresPresentationDAO {
|
||||
@ -31,24 +74,107 @@ object PresPresentationDAO {
|
||||
}
|
||||
}
|
||||
|
||||
def insertOrUpdate(meetingId: String, presentation: PresentationInPod) = {
|
||||
DatabaseConnection.db.run(
|
||||
TableQuery[PresPresentationDbTableDef].insertOrUpdate(
|
||||
def insertToken(meetingId: String, userId: String, temporaryId: String, presentationId: String, uploadToken: String, filename: String) = {
|
||||
val dbRun = DatabaseConnection.db.run(
|
||||
TableQuery[PresPresentationDbTableDef].forceInsert(
|
||||
PresPresentationDbModel(
|
||||
presentationId = presentation.id,
|
||||
presentationId = presentationId,
|
||||
meetingId = meetingId,
|
||||
uploadUserId = Some(userId),
|
||||
uploadTemporaryId = Some(temporaryId),
|
||||
uploadToken = Some(uploadToken),
|
||||
name = filename,
|
||||
filenameConverted = "",
|
||||
isDefault = false,
|
||||
current = false, //Set after pages were inserted
|
||||
downloadable = presentation.downloadable,
|
||||
removable = presentation.removable,
|
||||
uploadCompleted = presentation.uploadCompleted,
|
||||
numPages = presentation.numPages,
|
||||
errorMsgKey = presentation.errorMsgKey,
|
||||
errorDetails = presentation.errorDetails.toJson.asJsObject.compactPrint
|
||||
downloadable = false,
|
||||
downloadFileExtension = None,
|
||||
downloadFileUri = None,
|
||||
removable = false,
|
||||
uploadInProgress = false,
|
||||
uploadCompleted = false,
|
||||
totalPages = 0,
|
||||
uploadErrorMsgKey = None,
|
||||
uploadErrorDetailsJson = None,
|
||||
exportToChatStatus = None,
|
||||
exportToChatCurrentPage = None,
|
||||
exportToChatHasError = None,
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
dbRun.onComplete {
|
||||
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) inserted on Presentation table!")
|
||||
case Failure(e) => DatabaseConnection.logger.error(s"Error inserting Presentation: $e")
|
||||
}
|
||||
|
||||
dbRun
|
||||
}
|
||||
|
||||
def updateConversionStarted(meetingId: String, presentation: PresentationInPod) = {
|
||||
val checkAndInsert = DatabaseConnection.db.run(
|
||||
TableQuery[PresPresentationDbTableDef]
|
||||
.filter(_.presentationId === presentation.id).exists.result).flatMap { exists =>
|
||||
if (!exists) {
|
||||
insertToken(meetingId, "", "", presentation.id, "", presentation.name)
|
||||
} else {
|
||||
Future.successful(0)
|
||||
}
|
||||
}
|
||||
|
||||
checkAndInsert.flatMap { _ =>
|
||||
DatabaseConnection.db.run(
|
||||
TableQuery[PresPresentationDbTableDef]
|
||||
.filter(_.presentationId === presentation.id)
|
||||
.map(p => (
|
||||
p.name,
|
||||
p.filenameConverted,
|
||||
p.isDefault,
|
||||
p.downloadable,
|
||||
p.downloadFileExtension,
|
||||
p.removable,
|
||||
p.uploadInProgress,
|
||||
p.uploadCompleted,
|
||||
p.totalPages))
|
||||
.update(
|
||||
(presentation.name,
|
||||
presentation.filenameConverted,
|
||||
presentation.default,
|
||||
presentation.downloadable,
|
||||
presentation.downloadFileExtension match {
|
||||
case "" => None
|
||||
case downloadFileExtension => Some(downloadFileExtension)
|
||||
},
|
||||
presentation.removable,
|
||||
!presentation.uploadCompleted,
|
||||
presentation.uploadCompleted,
|
||||
presentation.numPages
|
||||
))
|
||||
)
|
||||
}.onComplete {
|
||||
case Success(rowAffected) => DatabaseConnection.logger.debug(s"$rowAffected row(s) updated basicData on PresPresentation table")
|
||||
case Failure(e) => DatabaseConnection.logger.error(s"Error updating basicData on PresPresentation: $e")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def updatePages(presentation: PresentationInPod) = {
|
||||
DatabaseConnection.db.run(
|
||||
TableQuery[PresPresentationDbTableDef]
|
||||
.filter(_.presentationId === presentation.id)
|
||||
.map(p => (p.downloadFileExtension, p.uploadInProgress, p.uploadCompleted, p.totalPages))
|
||||
.update((
|
||||
presentation.downloadFileExtension match {
|
||||
case "" => None
|
||||
case downloadFileExtension => Some(downloadFileExtension)
|
||||
},
|
||||
!presentation.uploadCompleted,
|
||||
presentation.uploadCompleted,
|
||||
presentation.numPages,
|
||||
))
|
||||
).onComplete {
|
||||
case Success(rowsAffected) => {
|
||||
DatabaseConnection.logger.debug(s"$rowsAffected row(s) inserted on Presentation table!")
|
||||
DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated on PresPresentation table!")
|
||||
|
||||
DatabaseConnection.db.run(DBIO.sequence(
|
||||
for {
|
||||
@ -59,7 +185,8 @@ object PresPresentationDAO {
|
||||
pageId = page._2.id,
|
||||
presentationId = presentation.id,
|
||||
num = page._2.num,
|
||||
urls = page._2.urls.toJson.asJsObject.compactPrint,
|
||||
urlsJson = page._2.urls.toJson,
|
||||
content = page._2.content,
|
||||
slideRevealed = page._2.current,
|
||||
current = page._2.current,
|
||||
xOffset = page._2.xOffset,
|
||||
@ -72,22 +199,23 @@ object PresPresentationDAO {
|
||||
viewBoxHeight = 1,
|
||||
maxImageWidth = 1440,
|
||||
maxImageHeight = 1080,
|
||||
converted = page._2.converted
|
||||
uploadCompleted = page._2.converted
|
||||
)
|
||||
)
|
||||
}
|
||||
).transactionally)
|
||||
.onComplete {
|
||||
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) inserted on PresentationPage table!")
|
||||
case Failure(e) => DatabaseConnection.logger.debug(s"Error inserting PresentationPage: $e")
|
||||
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated on PresentationPage table!")
|
||||
case Failure(e) => DatabaseConnection.logger.debug(s"Error updating PresentationPage: $e")
|
||||
}
|
||||
|
||||
//Set current
|
||||
if (presentation.current) {
|
||||
setCurrentPres(presentation.id)
|
||||
}
|
||||
|
||||
}
|
||||
case Failure(e) => DatabaseConnection.logger.error(s"Error inserting Presentation: $e")
|
||||
case Failure(e) => DatabaseConnection.logger.debug(s"Error updating user: $e")
|
||||
}
|
||||
}
|
||||
|
||||
@ -95,23 +223,83 @@ object PresPresentationDAO {
|
||||
DatabaseConnection.db.run(
|
||||
sqlu"""UPDATE pres_presentation SET
|
||||
"current" = (case when "presentationId" = ${presentationId} then true else false end)
|
||||
WHERE "meetingId" = (select "meetingId" from pres_presentation where "presentationId" = ${presentationId})"""
|
||||
WHERE "meetingId" = (select "meetingId" from pres_presentation where "presentationId" = ${presentationId})
|
||||
AND exists (select 1 from pres_page where "presentationId" = ${presentationId} AND "current" IS true)"""
|
||||
).onComplete {
|
||||
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated current on PresPresentation table")
|
||||
case Failure(e) => DatabaseConnection.logger.error(s"Error updating current on PresPresentation: $e")
|
||||
}
|
||||
}
|
||||
|
||||
def updateDownloadable(presentationId: String, downloadable : Boolean, downloadableExtension: String) = {
|
||||
DatabaseConnection.db.run(
|
||||
TableQuery[PresPresentationDbTableDef]
|
||||
.filter(_.presentationId === presentationId)
|
||||
.map(p => (p.downloadable, p.downloadFileExtension))
|
||||
.update((downloadable, Some(downloadableExtension)))
|
||||
).onComplete {
|
||||
case Success(rowAffected) => DatabaseConnection.logger.debug(s"$rowAffected row(s) updated downloadable on PresPresentation table")
|
||||
case Failure(e) => DatabaseConnection.logger.error(s"Error updating downloadable on PresPresentation: $e")
|
||||
}
|
||||
}
|
||||
|
||||
def updateDownloadUri(presentationId: String, downloadFileUri: String) = {
|
||||
DatabaseConnection.db.run(
|
||||
TableQuery[PresPresentationDbTableDef]
|
||||
.filter(_.presentationId === presentationId)
|
||||
.map(p => p.downloadFileUri)
|
||||
.update(Some(downloadFileUri))
|
||||
).onComplete {
|
||||
case Success(rowAffected) => DatabaseConnection.logger.debug(s"$rowAffected row(s) updated originalFileURI on PresPresentation table")
|
||||
case Failure(e) => DatabaseConnection.logger.error(s"Error updating originalFileURI on PresPresentation: $e")
|
||||
}
|
||||
}
|
||||
|
||||
def updateErrors(presentationId: String, errorMsgKey: String, errorDetails: scala.collection.immutable.Map[String, String]) = {
|
||||
DatabaseConnection.db.run(
|
||||
TableQuery[PresPresentationDbTableDef]
|
||||
.filter(_.presentationId === presentationId)
|
||||
.map(p => (p.errorMsgKey, p.errorDetails))
|
||||
.update(errorMsgKey, errorDetails.toJson.asJsObject.compactPrint)
|
||||
.map(p => (p.uploadErrorMsgKey, p.uploadErrorDetailsJson))
|
||||
.update(Some(errorMsgKey), Some(errorDetails.toJson))
|
||||
).onComplete {
|
||||
case Success(rowAffected) => DatabaseConnection.logger.debug(s"$rowAffected row(s) updated errorMsgKey on PresPresentation table")
|
||||
case Failure(e) => DatabaseConnection.logger.error(s"Error updating errorMsgKey on PresPresentation: $e")
|
||||
}
|
||||
}
|
||||
|
||||
def updateExportToChat(presentationId: String, exportToChatStatus: String, exportToChatCurrentPage: Int, exportToChatHasError: Boolean) = {
|
||||
DatabaseConnection.db.run(
|
||||
TableQuery[PresPresentationDbTableDef]
|
||||
.filter(_.presentationId === presentationId)
|
||||
.map(p => (p.exportToChatStatus, p.exportToChatCurrentPage, p.exportToChatHasError))
|
||||
.update(Some(exportToChatStatus), Some(exportToChatCurrentPage), Some(exportToChatHasError))
|
||||
).onComplete {
|
||||
case Success(rowAffected) => DatabaseConnection.logger.debug(s"$rowAffected row(s) updated exportToChat on PresPresentation table")
|
||||
case Failure(e) => DatabaseConnection.logger.error(s"Error updating exportToChat on PresPresentation: $e")
|
||||
}
|
||||
}
|
||||
|
||||
def updateExportToChatStatus(presentationId: String, exportToChatStatus: String) = {
|
||||
DatabaseConnection.db.run(
|
||||
TableQuery[PresPresentationDbTableDef]
|
||||
.filter(_.presentationId === presentationId)
|
||||
.map(p => p.exportToChatStatus)
|
||||
.update(Some(exportToChatStatus))
|
||||
).onComplete {
|
||||
case Success(rowAffected) => DatabaseConnection.logger.debug(s"$rowAffected row(s) updated exportToChatStatus on PresPresentation table")
|
||||
case Failure(e) => DatabaseConnection.logger.error(s"Error updating exportToChatStatus on PresPresentation: $e")
|
||||
}
|
||||
}
|
||||
|
||||
def delete(presentationId: String) = {
|
||||
DatabaseConnection.db.run(
|
||||
TableQuery[PresPresentationDbTableDef]
|
||||
.filter(_.presentationId === presentationId)
|
||||
.delete
|
||||
).onComplete {
|
||||
case Success(rowAffected) => DatabaseConnection.logger.debug(s"$rowAffected row(s) deleted presentation on PresPresentation table")
|
||||
case Failure(e) => DatabaseConnection.logger.error(s"Error deleting presentation on PresPresentation: $e")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
package org.bigbluebutton.core.db
|
||||
|
||||
import org.bigbluebutton.core.apps.TimerModel
|
||||
import org.bigbluebutton.core.apps.TimerModel.{getAccumulated, getEndedAt, getIsACtive, getRunning, getStartedAt, getStopwatch, getTime, getTrack}
|
||||
import org.bigbluebutton.core.apps.TimerModel.{getAccumulated, getEndedAt, getIsActive, getRunning, getStartedAt, getStopwatch, getTime, getTrack}
|
||||
import slick.jdbc.PostgresProfile.api._
|
||||
|
||||
import scala.util.{Failure, Success}
|
||||
@ -59,7 +59,7 @@ object TimerDAO {
|
||||
TableQuery[TimerDbTableDef]
|
||||
.filter(_.meetingId === meetingId)
|
||||
.map(t => (t.stopwatch, t.running, t.active, t.time, t.accumulated, t.startedAt, t.endedAt, t.songTrack))
|
||||
.update((getStopwatch(timerModel), getRunning(timerModel), getIsACtive(timerModel), getTime(timerModel), getAccumulated(timerModel), getStartedAt(timerModel), getEndedAt(timerModel), getTrack(timerModel))
|
||||
.update((getStopwatch(timerModel), getRunning(timerModel), getIsActive(timerModel), getTime(timerModel), getAccumulated(timerModel), getStartedAt(timerModel), getEndedAt(timerModel), getTrack(timerModel))
|
||||
)
|
||||
).onComplete {
|
||||
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated on Timer table!")
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
@ -19,7 +19,7 @@ class UserConnectionStatusDbTableDef(tag: Tag) extends Table[UserConnectionStatu
|
||||
val connectionAliveAt = column[Option[java.sql.Timestamp]]("connectionAliveAt")
|
||||
}
|
||||
|
||||
object UserConnectionStatusdDAO {
|
||||
object UserConnectionStatusDAO {
|
||||
|
||||
def insert(meetingId: String, userId: String) = {
|
||||
DatabaseConnection.db.run(
|
||||
|
@ -1,5 +1,5 @@
|
||||
package org.bigbluebutton.core.db
|
||||
import org.bigbluebutton.core.models.{RegisteredUser}
|
||||
import org.bigbluebutton.core.models.{RegisteredUser, VoiceUserState}
|
||||
import slick.jdbc.PostgresProfile.api._
|
||||
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
@ -13,21 +13,25 @@ case class UserDbModel(
|
||||
role: String,
|
||||
avatar: String = "",
|
||||
color: String = "",
|
||||
sessionToken: String = "",
|
||||
authed: Boolean = false,
|
||||
joined: Boolean = false,
|
||||
joinErrorMessage: Option[String],
|
||||
joinErrorCode: Option[String],
|
||||
banned: Boolean = false,
|
||||
loggedOut: Boolean = false,
|
||||
guest: Boolean,
|
||||
guestStatus: String,
|
||||
registeredOn: Long,
|
||||
excludeFromDashboard: Boolean,
|
||||
enforceLayout: Option[String],
|
||||
)
|
||||
|
||||
|
||||
|
||||
class UserDbTableDef(tag: Tag) extends Table[UserDbModel](tag, None, "user") {
|
||||
override def * = (
|
||||
userId,extId,meetingId,name,role,avatar,color,authed,joined,banned,loggedOut,guest,guestStatus,registeredOn,excludeFromDashboard) <> (UserDbModel.tupled, UserDbModel.unapply)
|
||||
userId,extId,meetingId,name,role,avatar,color, sessionToken, authed,joined,joinErrorCode, joinErrorMessage, banned,loggedOut,guest,guestStatus,registeredOn,excludeFromDashboard, enforceLayout) <> (UserDbModel.tupled, UserDbModel.unapply)
|
||||
val userId = column[String]("userId", O.PrimaryKey)
|
||||
val extId = column[String]("extId")
|
||||
val meetingId = column[String]("meetingId")
|
||||
@ -35,14 +39,18 @@ class UserDbTableDef(tag: Tag) extends Table[UserDbModel](tag, None, "user") {
|
||||
val role = column[String]("role")
|
||||
val avatar = column[String]("avatar")
|
||||
val color = column[String]("color")
|
||||
val sessionToken = column[String]("sessionToken")
|
||||
val authed = column[Boolean]("authed")
|
||||
val joined = column[Boolean]("joined")
|
||||
val joinErrorCode = column[Option[String]]("joinErrorCode")
|
||||
val joinErrorMessage = column[Option[String]]("joinErrorMessage")
|
||||
val banned = column[Boolean]("banned")
|
||||
val loggedOut = column[Boolean]("loggedOut")
|
||||
val guest = column[Boolean]("guest")
|
||||
val guestStatus = column[String]("guestStatus")
|
||||
val registeredOn = column[Long]("registeredOn")
|
||||
val excludeFromDashboard = column[Boolean]("excludeFromDashboard")
|
||||
val enforceLayout = column[Option[String]]("enforceLayout")
|
||||
}
|
||||
|
||||
object UserDAO {
|
||||
@ -57,23 +65,30 @@ object UserDAO {
|
||||
role = regUser.role,
|
||||
avatar = regUser.avatarURL,
|
||||
color = regUser.color,
|
||||
sessionToken = regUser.sessionToken,
|
||||
authed = regUser.authed,
|
||||
joined = regUser.joined,
|
||||
joinErrorCode = None,
|
||||
joinErrorMessage = None,
|
||||
banned = regUser.banned,
|
||||
loggedOut = regUser.loggedOut,
|
||||
guest = regUser.guest,
|
||||
guestStatus = regUser.guestStatus,
|
||||
registeredOn = regUser.registeredOn,
|
||||
excludeFromDashboard = regUser.excludeFromDashboard
|
||||
excludeFromDashboard = regUser.excludeFromDashboard,
|
||||
enforceLayout = regUser.enforceLayout match {
|
||||
case "" => None
|
||||
case enforceLayout: String => Some(enforceLayout)
|
||||
}
|
||||
)
|
||||
)
|
||||
).onComplete {
|
||||
case Success(rowsAffected) => {
|
||||
DatabaseConnection.logger.debug(s"$rowsAffected row(s) inserted in User table!")
|
||||
ChatUserDAO.insertUserPublicChat(meetingId, regUser.id)
|
||||
UserConnectionStatusdDAO.insert(meetingId, regUser.id)
|
||||
UserConnectionStatusDAO.insert(meetingId, regUser.id)
|
||||
UserCustomParameterDAO.insert(regUser.id, regUser.customParameters)
|
||||
UserLocalSettingsDAO.insert(regUser.id, meetingId)
|
||||
UserClientSettingsDAO.insert(regUser.id, meetingId)
|
||||
ChatUserDAO.insertUserPublicChat(meetingId, regUser.id)
|
||||
}
|
||||
case Failure(e) => DatabaseConnection.logger.debug(s"Error inserting user: $e")
|
||||
}
|
||||
@ -91,18 +106,33 @@ object UserDAO {
|
||||
}
|
||||
}
|
||||
|
||||
def updateVoiceUserJoined(voiceUserState: VoiceUserState) = {
|
||||
|
||||
DatabaseConnection.db.run(
|
||||
TableQuery[UserDbTableDef]
|
||||
.filter(_.userId === voiceUserState.intId)
|
||||
.map(u => (u.guest, u.guestStatus, u.authed, u.joined))
|
||||
.update((false, "ALLOW", true, true))
|
||||
).onComplete {
|
||||
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated on user voice table!")
|
||||
case Failure(e) => DatabaseConnection.logger.debug(s"Error updating user voice: $e")
|
||||
}
|
||||
}
|
||||
|
||||
def updateJoinError(userId: String, joinErrorCode: String, joinErrorMessage: String) = {
|
||||
DatabaseConnection.db.run(
|
||||
TableQuery[UserDbTableDef]
|
||||
.filter(_.userId === userId)
|
||||
.map(u => (u.joined, u.joinErrorCode, u.joinErrorMessage))
|
||||
.update((false, Some(joinErrorCode), Some(joinErrorMessage)))
|
||||
).onComplete {
|
||||
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated on user (Joined) table!")
|
||||
case Failure(e) => DatabaseConnection.logger.debug(s"Error updating user (Joined): $e")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def delete(intId: String) = {
|
||||
// DatabaseConnection.db.run(
|
||||
// TableQuery[UserDbTableDef]
|
||||
// .filter(_.userId === intId)
|
||||
// .delete
|
||||
// ).onComplete {
|
||||
// case Success(rowsAffected) => DatabaseConnection.logger.debug(s"User ${intId} deleted")
|
||||
// case Failure(e) => DatabaseConnection.logger.error(s"Error deleting user ${intId}: $e")
|
||||
// }
|
||||
|
||||
DatabaseConnection.db.run(
|
||||
TableQuery[UserDbTableDef]
|
||||
.filter(_.userId === intId)
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
@ -9,17 +9,17 @@ import scala.util.{ Failure, Success }
|
||||
case class UserReactionDbModel(
|
||||
userId: String,
|
||||
reactionEmoji: String,
|
||||
duration: Int,
|
||||
durationInSeconds: Int,
|
||||
createdAt: java.sql.Timestamp
|
||||
)
|
||||
|
||||
class UserReactionDbTableDef(tag: Tag) extends Table[UserReactionDbModel](tag, "user_reaction") {
|
||||
val userId = column[String]("userId")
|
||||
val reactionEmoji = column[String]("reactionEmoji")
|
||||
val duration = column[Int]("duration")
|
||||
val durationInSeconds = column[Int]("durationInSeconds")
|
||||
val createdAt = column[java.sql.Timestamp]("createdAt")
|
||||
|
||||
override def * : ProvenShape[UserReactionDbModel] = (userId, reactionEmoji, duration, createdAt) <> (UserReactionDbModel.tupled, UserReactionDbModel.unapply)
|
||||
override def * : ProvenShape[UserReactionDbModel] = (userId, reactionEmoji, durationInSeconds, createdAt) <> (UserReactionDbModel.tupled, UserReactionDbModel.unapply)
|
||||
}
|
||||
|
||||
object UserReactionDAO {
|
||||
@ -29,7 +29,7 @@ object UserReactionDAO {
|
||||
UserReactionDbModel(
|
||||
userId = userId,
|
||||
reactionEmoji = reactionEmoji,
|
||||
duration = 60,
|
||||
durationInSeconds = 60,
|
||||
createdAt = new java.sql.Timestamp(System.currentTimeMillis())
|
||||
)
|
||||
)
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -19,7 +19,6 @@ case class UserVoiceDbModel(
|
||||
talking: Boolean,
|
||||
floor: Boolean,
|
||||
lastFloorTime: String,
|
||||
voiceConf: String,
|
||||
startTime: Option[Long],
|
||||
endTime: Option[Long],
|
||||
)
|
||||
@ -27,7 +26,7 @@ case class UserVoiceDbModel(
|
||||
class UserVoiceDbTableDef(tag: Tag) extends Table[UserVoiceDbModel](tag, None, "user_voice") {
|
||||
override def * = (
|
||||
userId, voiceUserId, callerName, callerNum, callingWith, joined, listenOnly,
|
||||
muted, spoke, talking, floor, lastFloorTime, voiceConf, startTime, endTime
|
||||
muted, spoke, talking, floor, lastFloorTime, startTime, endTime
|
||||
) <> (UserVoiceDbModel.tupled, UserVoiceDbModel.unapply)
|
||||
val userId = column[String]("userId", O.PrimaryKey)
|
||||
val voiceUserId = column[String]("voiceUserId")
|
||||
@ -42,6 +41,9 @@ class UserVoiceDbTableDef(tag: Tag) extends Table[UserVoiceDbModel](tag, None, "
|
||||
val floor = column[Boolean]("floor")
|
||||
val lastFloorTime = column[String]("lastFloorTime")
|
||||
val voiceConf = column[String]("voiceConf")
|
||||
val voiceConfCallSession = column[String]("voiceConfCallSession")
|
||||
val voiceConfClientSession = column[String]("voiceConfClientSession")
|
||||
val voiceConfCallState = column[String]("voiceConfCallState")
|
||||
val startTime = column[Option[Long]]("startTime")
|
||||
val endTime = column[Option[Long]]("endTime")
|
||||
}
|
||||
@ -64,7 +66,6 @@ object UserVoiceDAO {
|
||||
talking = voiceUserState.talking,
|
||||
floor = voiceUserState.floor,
|
||||
lastFloorTime = voiceUserState.lastFloorTime,
|
||||
voiceConf = "",
|
||||
startTime = None,
|
||||
endTime = None
|
||||
)
|
||||
@ -85,7 +86,7 @@ object UserVoiceDAO {
|
||||
.update((voiceUserState.listenOnly, voiceUserState.muted, voiceUserState.floor, voiceUserState.lastFloorTime))
|
||||
).onComplete {
|
||||
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated on user_voice table!")
|
||||
case Failure(e) => DatabaseConnection.logger.error(s"Error updating user: $e")
|
||||
case Failure(e) => DatabaseConnection.logger.error(s"Error updating user_voice: $e")
|
||||
}
|
||||
}
|
||||
|
||||
@ -113,7 +114,19 @@ object UserVoiceDAO {
|
||||
}
|
||||
}
|
||||
|
||||
def deleteUser(userId: String) = {
|
||||
def delete(intId: String) = {
|
||||
DatabaseConnection.db.run(
|
||||
TableQuery[UserDbTableDef]
|
||||
.filter(_.userId === intId)
|
||||
.map(u => (u.loggedOut))
|
||||
.update((true))
|
||||
).onComplete {
|
||||
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated loggedOut=true on user table!")
|
||||
case Failure(e) => DatabaseConnection.logger.error(s"Error updating loggedOut=true user: $e")
|
||||
}
|
||||
}
|
||||
|
||||
def deleteUserVoice(userId: String) = {
|
||||
//Meteor sets this props instead of removing
|
||||
// muted: false
|
||||
// talking: false
|
||||
@ -124,10 +137,11 @@ object UserVoiceDAO {
|
||||
DatabaseConnection.db.run(
|
||||
TableQuery[UserVoiceDbTableDef]
|
||||
.filter(_.userId === userId)
|
||||
.delete
|
||||
.map(u => (u.muted, u.talking, u.listenOnly, u.joined, u.spoke, u.startTime, u.endTime))
|
||||
.update((false, false, false, false, false, None, None))
|
||||
).onComplete {
|
||||
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"Voice of user ${userId} deleted")
|
||||
case Failure(e) => DatabaseConnection.logger.error(s"Error deleting voice: $e")
|
||||
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"Voice of user ${userId} deleted (joined=false)")
|
||||
case Failure(e) => DatabaseConnection.logger.error(s"Error deleting voice user: $e")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,6 @@
|
||||
package org.bigbluebutton.core.models
|
||||
|
||||
import org.bigbluebutton.common2.domain.PageVO
|
||||
import org.bigbluebutton.core.models.PresentationInPod
|
||||
import org.bigbluebutton.core.util.RandomStringGenerator
|
||||
import org.bigbluebutton.common2.msgs.AnnotationVO
|
||||
import org.bigbluebutton.core.db.{ PresPageDAO, PresPresentationDAO }
|
||||
|
||||
object PresentationPodFactory {
|
||||
@ -25,6 +22,7 @@ case class PresentationPage(
|
||||
id: String,
|
||||
num: Int,
|
||||
urls: Map[String, String],
|
||||
content: String,
|
||||
current: Boolean = false,
|
||||
xOffset: Double = 0,
|
||||
yOffset: Double = 0,
|
||||
@ -64,9 +62,11 @@ object PresentationInPod {
|
||||
case class PresentationInPod(
|
||||
id: String,
|
||||
name: String,
|
||||
default: Boolean = false,
|
||||
current: Boolean = false,
|
||||
pages: scala.collection.immutable.Map[String, PresentationPage],
|
||||
downloadable: Boolean,
|
||||
downloadFileExtension: String = "",
|
||||
removable: Boolean,
|
||||
filenameConverted: String = "",
|
||||
uploadCompleted: Boolean,
|
||||
@ -118,18 +118,18 @@ case class PresentationPod(id: String, currentPresenter: String,
|
||||
Some(tempPod)
|
||||
}
|
||||
|
||||
def setPresentationDownloadable(presentationId: String, downloadable: Boolean): Option[PresentationPod] = {
|
||||
def setPresentationDownloadable(presentationId: String, downloadable: Boolean, downloadFileExtension: String): Option[PresentationPod] = {
|
||||
var tempPod: PresentationPod = this
|
||||
presentations.values foreach (curPres => { // unset previous current presentation
|
||||
if (curPres.id != presentationId) {
|
||||
val newPres = curPres.copy(downloadable = downloadable)
|
||||
val newPres = curPres.copy(downloadable = downloadable, downloadFileExtension = downloadFileExtension)
|
||||
tempPod = tempPod.addPresentation(newPres)
|
||||
}
|
||||
})
|
||||
|
||||
presentations.get(presentationId) match { // set new current presentation
|
||||
case Some(pres) =>
|
||||
val cp = pres.copy(downloadable = downloadable)
|
||||
val cp = pres.copy(downloadable = downloadable, downloadFileExtension = downloadFileExtension)
|
||||
tempPod = tempPod.addPresentation(cp)
|
||||
case None => None
|
||||
}
|
||||
@ -235,10 +235,10 @@ case class PresentationPodManager(presentationPods: collection.immutable.Map[Str
|
||||
}
|
||||
}
|
||||
|
||||
def setPresentationDownloadableInPod(podId: String, presentationId: String, downloadable: Boolean): PresentationPodManager = {
|
||||
def setPresentationDownloadableInPod(podId: String, presentationId: String, downloadable: Boolean, downloadFileExtension: String): PresentationPodManager = {
|
||||
val updatedManager = for {
|
||||
pod <- getPod(podId)
|
||||
podWithAdjustedDownloadablePresentation <- pod.setPresentationDownloadable(presentationId, downloadable)
|
||||
podWithAdjustedDownloadablePresentation <- pod.setPresentationDownloadable(presentationId, downloadable, downloadFileExtension)
|
||||
|
||||
} yield {
|
||||
updatePresentationPod(podWithAdjustedDownloadablePresentation)
|
||||
|
@ -7,7 +7,8 @@ import org.bigbluebutton.core.domain.BreakoutRoom2x
|
||||
object RegisteredUsers {
|
||||
def create(userId: String, extId: String, name: String, roles: String,
|
||||
authToken: String, sessionToken: String, avatar: String, color: String, guest: Boolean, authenticated: Boolean,
|
||||
guestStatus: String, excludeFromDashboard: Boolean, customParameters: Map[String, String], loggedOut: Boolean): RegisteredUser = {
|
||||
guestStatus: String, excludeFromDashboard: Boolean, enforceLayout: String,
|
||||
customParameters: Map[String, String], loggedOut: Boolean): RegisteredUser = {
|
||||
new RegisteredUser(
|
||||
userId,
|
||||
extId,
|
||||
@ -25,6 +26,7 @@ object RegisteredUsers {
|
||||
0,
|
||||
false,
|
||||
false,
|
||||
enforceLayout,
|
||||
customParameters,
|
||||
loggedOut,
|
||||
)
|
||||
@ -214,6 +216,7 @@ case class RegisteredUser(
|
||||
lastAuthTokenValidatedOn: Long,
|
||||
joined: Boolean,
|
||||
banned: Boolean,
|
||||
enforceLayout: String,
|
||||
customParameters: Map[String,String],
|
||||
loggedOut: Boolean,
|
||||
lastBreakoutRoom: BreakoutRoom2x = null,
|
||||
|
@ -39,7 +39,7 @@ object VoiceUsers {
|
||||
}
|
||||
|
||||
def removeWithIntId(users: VoiceUsers, intId: String): Option[VoiceUserState] = {
|
||||
UserVoiceDAO.deleteUser(intId)
|
||||
UserVoiceDAO.deleteUserVoice(intId)
|
||||
users.remove(intId)
|
||||
}
|
||||
|
||||
|
@ -346,8 +346,6 @@ class ReceivedJsonMsgHandlerActor(
|
||||
routeGenericMsg[CreateNewPresentationPodPubMsg](envelope, jsonNode)
|
||||
case RemovePresentationPodPubMsg.NAME =>
|
||||
routeGenericMsg[RemovePresentationPodPubMsg](envelope, jsonNode)
|
||||
case SetPresenterInPodReqMsg.NAME =>
|
||||
routeGenericMsg[SetPresenterInPodReqMsg](envelope, jsonNode)
|
||||
|
||||
// Caption
|
||||
case EditCaptionHistoryPubMsg.NAME =>
|
||||
@ -417,6 +415,10 @@ class ReceivedJsonMsgHandlerActor(
|
||||
case CreateGroupChatReqMsg.NAME =>
|
||||
routeGenericMsg[CreateGroupChatReqMsg](envelope, jsonNode)
|
||||
|
||||
//Plugin
|
||||
case DispatchPluginDataChannelMessageMsg.NAME =>
|
||||
routeGenericMsg[DispatchPluginDataChannelMessageMsg](envelope, jsonNode)
|
||||
|
||||
// ExternalVideo
|
||||
case StartExternalVideoPubMsg.NAME =>
|
||||
routeGenericMsg[StartExternalVideoPubMsg](envelope, jsonNode)
|
||||
@ -447,6 +449,13 @@ class ReceivedJsonMsgHandlerActor(
|
||||
case TimerEndedPubMsg.NAME =>
|
||||
routeGenericMsg[TimerEndedPubMsg](envelope, jsonNode)
|
||||
|
||||
// Messages from Graphql Middleware
|
||||
case UserGraphqlConnectionStablishedSysMsg.NAME =>
|
||||
route[UserGraphqlConnectionStablishedSysMsg](meetingManagerChannel, envelope, jsonNode)
|
||||
|
||||
case UserGraphqlConnectionClosedSysMsg.NAME =>
|
||||
route[UserGraphqlConnectionClosedSysMsg](meetingManagerChannel, envelope, jsonNode)
|
||||
|
||||
case _ =>
|
||||
log.error("Cannot route envelope name " + envelope.name)
|
||||
// do nothing
|
||||
|
@ -5,6 +5,8 @@ import org.bigbluebutton.core.apps._
|
||||
import org.bigbluebutton.core.models._
|
||||
import org.bigbluebutton.core2.MeetingStatus2x
|
||||
|
||||
import java.util
|
||||
|
||||
class LiveMeeting(
|
||||
val props: DefaultProps,
|
||||
val status: MeetingStatus2x,
|
||||
@ -23,5 +25,6 @@ class LiveMeeting(
|
||||
val webcams: Webcams,
|
||||
val voiceUsers: VoiceUsers,
|
||||
val users2x: Users2x,
|
||||
val guestsWaiting: GuestsWaiting
|
||||
val guestsWaiting: GuestsWaiting,
|
||||
val clientSettings: Map[String, Object],
|
||||
)
|
||||
|
@ -39,6 +39,7 @@ import org.bigbluebutton.common2.msgs
|
||||
import scala.concurrent.duration._
|
||||
import org.bigbluebutton.core.apps.layout.LayoutApp2x
|
||||
import org.bigbluebutton.core.apps.meeting.{ SyncGetMeetingInfoRespMsgHdlr, ValidateConnAuthTokenSysMsgHdlr }
|
||||
import org.bigbluebutton.core.apps.plugin.PluginHdlrs
|
||||
import org.bigbluebutton.core.apps.users.ChangeLockSettingsInMeetingCmdMsgHdlr
|
||||
import org.bigbluebutton.core.db.UserStateDAO
|
||||
import org.bigbluebutton.core.models.VoiceUsers.{ findAllFreeswitchCallers, findAllListenOnlyVoiceUsers }
|
||||
@ -137,6 +138,7 @@ class MeetingActor(
|
||||
val webcamApp2x = new WebcamApp2x
|
||||
val wbApp = new WhiteboardApp2x
|
||||
val timerApp2x = new TimerApp2x
|
||||
val pluginHdlrs = new PluginHdlrs
|
||||
|
||||
object ExpiryTrackerHelper extends MeetingExpiryTrackerHelper
|
||||
|
||||
@ -263,6 +265,7 @@ class MeetingActor(
|
||||
//=======================================
|
||||
// internal messages
|
||||
case msg: MonitorNumberOfUsersInternalMsg => handleMonitorNumberOfUsers(msg)
|
||||
case msg: SetPresenterInDefaultPodInternalMsg => state = presentationPodsApp.handleSetPresenterInDefaultPodInternalMsg(msg, state, liveMeeting, msgBus)
|
||||
|
||||
case msg: ExtendMeetingDuration => handleExtendMeetingDuration(msg)
|
||||
case msg: SendTimeRemainingAuditInternalMsg =>
|
||||
@ -529,7 +532,6 @@ class MeetingActor(
|
||||
case m: PresentationConversionCompletedSysPubMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
|
||||
case m: PdfConversionInvalidErrorSysPubMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
|
||||
case m: SetCurrentPagePubMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
|
||||
case m: SetPresenterInPodReqMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
|
||||
case m: RemovePresentationPubMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
|
||||
case m: SetPresentationDownloadablePubMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
|
||||
case m: PresentationConversionUpdateSysPubMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
|
||||
@ -591,6 +593,9 @@ class MeetingActor(
|
||||
state = groupChatApp.handle(m, state, liveMeeting, msgBus)
|
||||
updateUserLastActivity(m.body.msg.sender.id)
|
||||
|
||||
// Plugin
|
||||
case m: DispatchPluginDataChannelMessageMsg => pluginHdlrs.handle(m, state, liveMeeting)
|
||||
|
||||
// Webcams
|
||||
case m: UserBroadcastCamStartMsg => webcamApp2x.handle(m, liveMeeting, msgBus)
|
||||
case m: UserBroadcastCamStopMsg => webcamApp2x.handle(m, liveMeeting, msgBus)
|
||||
@ -867,6 +872,7 @@ class MeetingActor(
|
||||
val hasModeratorLeftRecently = (TimeUtil.timeNowInMs() - state.expiryTracker.endWhenNoModeratorDelayInMs) < state.expiryTracker.lastModeratorLeftOnInMs
|
||||
if (!hasModeratorLeftRecently) {
|
||||
log.info("Meeting will end due option endWhenNoModerator is enabled and all moderators have left the meeting. meetingId=" + props.meetingProp.intId)
|
||||
endAllBreakoutRooms(eventBus, liveMeeting, state, MeetingEndReason.ENDED_DUE_TO_NO_MODERATOR)
|
||||
sendEndMeetingDueToExpiry(
|
||||
MeetingEndReason.ENDED_DUE_TO_NO_MODERATOR,
|
||||
eventBus, outGW, liveMeeting,
|
||||
|
@ -1,6 +1,7 @@
|
||||
package org.bigbluebutton.core.running
|
||||
|
||||
import org.apache.pekko.actor.ActorContext
|
||||
import org.bigbluebutton.ClientSettings
|
||||
import org.bigbluebutton.common2.domain.DefaultProps
|
||||
import org.bigbluebutton.core.apps._
|
||||
import org.bigbluebutton.core.bus._
|
||||
@ -34,6 +35,7 @@ class RunningMeeting(val props: DefaultProps, outGW: OutMessageGateway,
|
||||
private val deskshareModel = new ScreenshareModel
|
||||
private val audioCaptions = new AudioCaptions
|
||||
private val timerModel = new TimerModel
|
||||
val clientSettings: Map[String, Object] = ClientSettings.getClientSettingsWithOverride(props.overrideClientSettings)
|
||||
|
||||
// meetingModel.setGuestPolicy(props.usersProp.guestPolicy)
|
||||
|
||||
@ -41,7 +43,7 @@ class RunningMeeting(val props: DefaultProps, outGW: OutMessageGateway,
|
||||
// easy to test.
|
||||
private val liveMeeting = new LiveMeeting(props, meetingStatux2x, deskshareModel, audioCaptions, timerModel,
|
||||
chatModel, externalVideoModel, layouts, pads, registeredUsers, polls2x, wbModel, presModel, captionModel,
|
||||
webcams, voiceUsers, users2x, guestsWaiting)
|
||||
webcams, voiceUsers, users2x, guestsWaiting, clientSettings)
|
||||
|
||||
GuestsWaiting.setGuestPolicy(
|
||||
liveMeeting.props.meetingProp.intId,
|
||||
|
@ -10,7 +10,7 @@ object RunningMeetings {
|
||||
|
||||
def add(meetings: RunningMeetings, meeting: RunningMeeting): RunningMeeting = {
|
||||
meetings.save(meeting)
|
||||
MeetingDAO.insert(meeting.props)
|
||||
MeetingDAO.insert(meeting.props, meeting.clientSettings)
|
||||
meeting
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
package org.bigbluebutton.core2.message.senders
|
||||
|
||||
import org.bigbluebutton.common2.domain.DefaultProps
|
||||
import org.bigbluebutton.common2.msgs.{ BbbCommonEnvCoreMsg, BbbCoreEnvelope, BbbCoreHeaderWithMeetingId, MessageTypes, Routing, ValidateConnAuthTokenSysRespMsg, ValidateConnAuthTokenSysRespMsgBody, NotifyAllInMeetingEvtMsg, NotifyAllInMeetingEvtMsgBody, NotifyRoleInMeetingEvtMsg, NotifyRoleInMeetingEvtMsgBody, NotifyUserInMeetingEvtMsg, NotifyUserInMeetingEvtMsgBody, _ }
|
||||
import org.bigbluebutton.core.models.GuestWaiting
|
||||
import org.bigbluebutton.common2.msgs.{ BbbCommonEnvCoreMsg, BbbCoreEnvelope, BbbCoreHeaderWithMeetingId, MessageTypes, NotifyAllInMeetingEvtMsg, NotifyAllInMeetingEvtMsgBody, NotifyRoleInMeetingEvtMsg, NotifyRoleInMeetingEvtMsgBody, NotifyUserInMeetingEvtMsg, NotifyUserInMeetingEvtMsgBody, Routing, ValidateConnAuthTokenSysRespMsg, ValidateConnAuthTokenSysRespMsgBody, _ }
|
||||
import org.bigbluebutton.core.models.{ GuestWaiting, PresentationPod }
|
||||
|
||||
object MsgBuilder {
|
||||
def buildGuestPolicyChangedEvtMsg(meetingId: String, userId: String, policy: String, setBy: String): BbbCommonEnvCoreMsg = {
|
||||
|
@ -56,7 +56,7 @@ object FakeUserGenerator {
|
||||
val color = "#ff6242"
|
||||
|
||||
val ru = RegisteredUsers.create(userId = id, extId, name, role,
|
||||
authToken, sessionToken, avatarURL, color, guest, authed, guestStatus = GuestStatus.ALLOW, false, Map(), false)
|
||||
authToken, sessionToken, avatarURL, color, guest, authed, guestStatus = GuestStatus.ALLOW, false, "", Map(), false)
|
||||
RegisteredUsers.add(users, ru, meetingId)
|
||||
ru
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
@ -397,10 +397,33 @@ class LearningDashboardActor(
|
||||
user <- findUserByIntId(meeting, msg.body.userId)
|
||||
} yield {
|
||||
if (msg.body.reactionEmoji != "none") {
|
||||
//Ignore multiple Reactions to prevent flooding
|
||||
val hasSameReactionInLast30Seconds = user.reactions.filter(r => {
|
||||
System.currentTimeMillis() - r.sentOn < (30 * 1000) && r.name == msg.body.reactionEmoji
|
||||
}).length > 0
|
||||
|
||||
if(!hasSameReactionInLast30Seconds) {
|
||||
val updatedUser = user.copy(reactions = user.reactions :+ Emoji(msg.body.reactionEmoji))
|
||||
val updatedMeeting = meeting.copy(users = meeting.users + (updatedUser.userKey -> updatedUser))
|
||||
|
||||
meetings += (updatedMeeting.intId -> updatedMeeting)
|
||||
|
||||
//Convert Reactions to legacy Emoji (while LearningDashboard doesn't support Reactions)
|
||||
val emoji = msg.body.reactionEmoji.codePointAt(0) match {
|
||||
case 128515 => "happy"
|
||||
case 128528 => "neutral"
|
||||
case 128577 => "sad"
|
||||
case 128077 => "thumbsUp"
|
||||
case 128078 => "thumbsDown"
|
||||
case 128079 => "applause"
|
||||
case _ => "none"
|
||||
}
|
||||
|
||||
if (emoji != "none") {
|
||||
val updatedUserWithEmoji = updatedUser.copy(emojis = user.emojis :+ Emoji(emoji))
|
||||
val updatedMeetingWithEmoji = meeting.copy(users = meeting.users + (updatedUserWithEmoji.userKey -> updatedUserWithEmoji))
|
||||
meetings += (updatedMeeting.intId -> updatedMeetingWithEmoji)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,9 +13,10 @@ object TestDataGen {
|
||||
val sessionToken = RandomStringGenerator.randomAlphanumericString(16)
|
||||
val avatarURL = "https://www." + RandomStringGenerator.randomAlphanumericString(32) + ".com/" +
|
||||
RandomStringGenerator.randomAlphanumericString(10) + ".png"
|
||||
val color = "#ff6242"
|
||||
|
||||
val ru = RegisteredUsers.create(userId = id, extId, name, role,
|
||||
authToken, sessionToken, avatarURL, guest, authed, GuestStatus.ALLOW, false)
|
||||
authToken, sessionToken, avatarURL, color, guest, authed, GuestStatus.ALLOW, false, "", Map(), false)
|
||||
|
||||
RegisteredUsers.add(users, ru, meetingId = "test")
|
||||
ru
|
||||
|
@ -27,6 +27,11 @@ pekko {
|
||||
}
|
||||
}
|
||||
|
||||
client {
|
||||
clientSettingsFilePath = "/usr/share/meteor/bundle/programs/server/assets/app/config/settings.yml"
|
||||
clientSettingsOverrideFilePath = "/etc/bigbluebutton/bbb-html5.yml"
|
||||
}
|
||||
|
||||
redis {
|
||||
host="127.0.0.1"
|
||||
port=6379
|
||||
|
@ -19,6 +19,7 @@ object Dependencies {
|
||||
val sl4j = "1.7.32"
|
||||
val pool = "2.11.1"
|
||||
val codec = "1.15"
|
||||
val jacksonDataFormat = "2.13.5"
|
||||
|
||||
// Redis
|
||||
val lettuce = "6.1.5.RELEASE"
|
||||
@ -40,6 +41,7 @@ object Dependencies {
|
||||
val commonsCodec = "commons-codec" % "commons-codec" % Versions.codec
|
||||
|
||||
val lettuceCore = "io.lettuce" % "lettuce-core" % Versions.lettuce
|
||||
val jacksonDataFormat = "com.fasterxml.jackson.dataformat" % "jackson-dataformat-yaml" % Versions.jacksonDataFormat
|
||||
}
|
||||
|
||||
object Test {
|
||||
@ -64,5 +66,6 @@ object Dependencies {
|
||||
Compile.sl4jApi,
|
||||
Compile.commonsCodec,
|
||||
Compile.apachePool2,
|
||||
Compile.lettuceCore) ++ testing
|
||||
Compile.lettuceCore,
|
||||
Compile.jacksonDataFormat) ++ testing
|
||||
}
|
||||
|
@ -89,7 +89,8 @@ case class DefaultProps(
|
||||
metadataProp: MetadataProp,
|
||||
lockSettingsProps: LockSettingsProps,
|
||||
systemProps: SystemProps,
|
||||
groups: Vector[GroupProps]
|
||||
groups: Vector[GroupProps],
|
||||
overrideClientSettings: String
|
||||
)
|
||||
|
||||
case class StartEndTimeStatus(startTime: Long, endTime: Long)
|
||||
|
@ -2,7 +2,7 @@ package org.bigbluebutton.common2.domain
|
||||
|
||||
case class PresentationVO(id: String, temporaryPresentationId: String, name: String, current: Boolean = false,
|
||||
pages: Vector[PageVO], downloadable: Boolean, removable: Boolean,
|
||||
isInitialPresentation: Boolean, filenameConverted: String)
|
||||
defaultPresentation: Boolean, filenameConverted: String)
|
||||
|
||||
case class PageVO(id: String, num: Int, thumbUri: String = "",
|
||||
txtUri: String, svgUri: String, current: Boolean = false, xOffset: Double = 0,
|
||||
@ -15,6 +15,7 @@ case class PresentationPageConvertedVO(
|
||||
id: String,
|
||||
num: Int,
|
||||
urls: Map[String, String],
|
||||
content: String,
|
||||
current: Boolean = false,
|
||||
width: Double = 1440D,
|
||||
height: Double = 1080D
|
||||
|
@ -11,6 +11,7 @@ object GroupChatMessageType {
|
||||
val POLL = "poll"
|
||||
val BREAKOUTROOM_MOD_MSG = "breakoutRoomModeratorMsg"
|
||||
val PUBLIC_CHAT_HIST_CLEARED = "publicChatHistoryCleared"
|
||||
val USER_AWAY_STATUS_MSG = "userAwayStatusMsg"
|
||||
}
|
||||
|
||||
case class GroupChatUser(id: String, name: String = "", role: String = "VIEWER")
|
||||
|
@ -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],
|
||||
)
|
@ -13,7 +13,7 @@ case class RemovePresentationPodPubMsgBody(podId: String)
|
||||
|
||||
object PresentationUploadTokenReqMsg { val NAME = "PresentationUploadTokenReqMsg" }
|
||||
case class PresentationUploadTokenReqMsg(header: BbbClientMsgHeader, body: PresentationUploadTokenReqMsgBody) extends StandardMsg
|
||||
case class PresentationUploadTokenReqMsgBody(podId: String, filename: String, temporaryPresentationId: String)
|
||||
case class PresentationUploadTokenReqMsgBody(podId: String, filename: String, uploadTemporaryId: String)
|
||||
|
||||
object GetAllPresentationPodsReqMsg { val NAME = "GetAllPresentationPodsReqMsg" }
|
||||
case class GetAllPresentationPodsReqMsg(header: BbbClientMsgHeader, body: GetAllPresentationPodsReqMsgBody) extends StandardMsg
|
||||
@ -23,10 +23,6 @@ object SetCurrentPagePubMsg { val NAME = "SetCurrentPagePubMsg" }
|
||||
case class SetCurrentPagePubMsg(header: BbbClientMsgHeader, body: SetCurrentPagePubMsgBody) extends StandardMsg
|
||||
case class SetCurrentPagePubMsgBody(podId: String, presentationId: String, pageId: String)
|
||||
|
||||
object SetPresenterInPodReqMsg { val NAME = "SetPresenterInPodReqMsg" }
|
||||
case class SetPresenterInPodReqMsg(header: BbbClientMsgHeader, body: SetPresenterInPodReqMsgBody) extends StandardMsg
|
||||
case class SetPresenterInPodReqMsgBody(podId: String, nextPresenterId: String)
|
||||
|
||||
object RemovePresentationPubMsg { val NAME = "RemovePresentationPubMsg" }
|
||||
case class RemovePresentationPubMsg(header: BbbClientMsgHeader, body: RemovePresentationPubMsgBody) extends StandardMsg
|
||||
case class RemovePresentationPubMsgBody(podId: String, presentationId: String)
|
||||
@ -139,7 +135,9 @@ case class PresentationPageConversionStartedSysMsgBody(
|
||||
podId: String,
|
||||
presentationId: String,
|
||||
current: Boolean,
|
||||
default: Boolean,
|
||||
presName: String,
|
||||
presFilenameConverted: String,
|
||||
downloadable: Boolean,
|
||||
removable: Boolean,
|
||||
authzToken: String,
|
||||
@ -221,7 +219,7 @@ case class PdfConversionInvalidErrorEvtMsgBody(podId: String, messageKey: String
|
||||
|
||||
object PresentationUploadTokenPassRespMsg { val NAME = "PresentationUploadTokenPassRespMsg" }
|
||||
case class PresentationUploadTokenPassRespMsg(header: BbbClientMsgHeader, body: PresentationUploadTokenPassRespMsgBody) extends StandardMsg
|
||||
case class PresentationUploadTokenPassRespMsgBody(podId: String, authzToken: String, filename: String, temporaryPresentationId: String)
|
||||
case class PresentationUploadTokenPassRespMsgBody(podId: String, authzToken: String, filename: String, temporaryPresentationId: String, presentationId: String)
|
||||
|
||||
object PresentationUploadTokenFailRespMsg { val NAME = "PresentationUploadTokenFailRespMsg" }
|
||||
case class PresentationUploadTokenFailRespMsg(header: BbbClientMsgHeader, body: PresentationUploadTokenFailRespMsgBody) extends StandardMsg
|
||||
@ -359,5 +357,5 @@ case class SyncGetPresentationPodsRespMsgBody(pods: Vector[PresentationPodVO])
|
||||
// ------------ akka-apps to bbb-common-web ------------
|
||||
object PresentationUploadTokenSysPubMsg { val NAME = "PresentationUploadTokenSysPubMsg" }
|
||||
case class PresentationUploadTokenSysPubMsg(header: BbbClientMsgHeader, body: PresentationUploadTokenSysPubMsgBody) extends BbbCoreMsg
|
||||
case class PresentationUploadTokenSysPubMsgBody(podId: String, authzToken: String, filename: String, meetingId: String)
|
||||
case class PresentationUploadTokenSysPubMsgBody(podId: String, authzToken: String, filename: String, meetingId: String, presentationId: String)
|
||||
// ------------ akka-apps to bbb-common-web ------------
|
||||
|
@ -242,6 +242,31 @@ case class InvalidateUserGraphqlConnectionSysMsg(
|
||||
) extends BbbCoreMsg
|
||||
case class InvalidateUserGraphqlConnectionSysMsgBody(meetingId: String, userId: String, sessionToken: String, reason: String)
|
||||
|
||||
/**
|
||||
* Sent from graphql-middleware to akka-apps
|
||||
*/
|
||||
|
||||
object UserGraphqlConnectionInvalidatedEvtMsg { val NAME = "UserGraphqlConnectionInvalidatedEvtMsg" }
|
||||
case class UserGraphqlConnectionInvalidatedEvtMsg(
|
||||
header: BbbCoreBaseHeader,
|
||||
body: UserGraphqlConnectionInvalidatedEvtMsgBody
|
||||
) extends BbbCoreMsg
|
||||
case class UserGraphqlConnectionInvalidatedEvtMsgBody(sessionToken: String, browserConnectionId: String)
|
||||
|
||||
object UserGraphqlConnectionStablishedSysMsg { val NAME = "UserGraphqlConnectionStablishedSysMsg" }
|
||||
case class UserGraphqlConnectionStablishedSysMsg(
|
||||
header: BbbCoreBaseHeader,
|
||||
body: UserGraphqlConnectionStablishedSysMsgBody
|
||||
) extends BbbCoreMsg
|
||||
case class UserGraphqlConnectionStablishedSysMsgBody(sessionToken: String, browserConnectionId: String)
|
||||
|
||||
object UserGraphqlConnectionClosedSysMsg { val NAME = "UserGraphqlConnectionClosedSysMsg" }
|
||||
case class UserGraphqlConnectionClosedSysMsg(
|
||||
header: BbbCoreBaseHeader,
|
||||
body: UserGraphqlConnectionClosedSysMsgBody
|
||||
) extends BbbCoreMsg
|
||||
case class UserGraphqlConnectionClosedSysMsgBody(sessionToken: String, browserConnectionId: String)
|
||||
|
||||
/**
|
||||
* Sent from akka-apps to bbb-web to inform a summary of the meeting activities
|
||||
*/
|
||||
|
@ -8,7 +8,7 @@ case class RegisterUserReqMsg(
|
||||
case class RegisterUserReqMsgBody(meetingId: String, intUserId: String, name: String, role: String,
|
||||
extUserId: String, authToken: String, sessionToken: String, avatarURL: String,
|
||||
guest: Boolean, authed: Boolean, guestStatus: String, excludeFromDashboard: Boolean,
|
||||
customParameters: Map[String, String])
|
||||
enforceLayout: String, customParameters: Map[String, String])
|
||||
|
||||
object UserRegisteredRespMsg { val NAME = "UserRegisteredRespMsg" }
|
||||
case class UserRegisteredRespMsg(
|
||||
@ -302,7 +302,7 @@ case class UserMobileFlagChangedEvtMsgBody(userId: String, mobile: Boolean)
|
||||
|
||||
object AssignPresenterReqMsg { val NAME = "AssignPresenterReqMsg" }
|
||||
case class AssignPresenterReqMsg(header: BbbClientMsgHeader, body: AssignPresenterReqMsgBody) extends StandardMsg
|
||||
case class AssignPresenterReqMsgBody(requesterId: String, newPresenterId: String, newPresenterName: String, assignedBy: String)
|
||||
case class AssignPresenterReqMsgBody(assignedBy: String, newPresenterId: String)
|
||||
|
||||
/**
|
||||
* Sent from client to change the video pin of the user in the meeting.
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -122,13 +122,19 @@ public class MeetingService implements MessageListener {
|
||||
public void registerUser(String meetingID, String internalUserId,
|
||||
String fullname, String role, String externUserID,
|
||||
String authToken, String sessionToken, String avatarURL, Boolean guest,
|
||||
Boolean authed, String guestStatus, Boolean excludeFromDashboard, Boolean leftGuestLobby, Map<String, String> customParameters) {
|
||||
handle(new RegisterUser(meetingID, internalUserId, fullname, role,
|
||||
externUserID, authToken, sessionToken, avatarURL, guest, authed, guestStatus, excludeFromDashboard, leftGuestLobby, customParameters));
|
||||
Boolean authed, String guestStatus, Boolean excludeFromDashboard, Boolean leftGuestLobby,
|
||||
String enforceLayout, Map<String, String> customParameters) {
|
||||
handle(
|
||||
new RegisterUser(meetingID, internalUserId, fullname, role,
|
||||
externUserID, authToken, sessionToken, avatarURL, guest, authed, guestStatus,
|
||||
excludeFromDashboard, leftGuestLobby, enforceLayout, customParameters
|
||||
)
|
||||
);
|
||||
|
||||
Meeting m = getMeeting(meetingID);
|
||||
if (m != null) {
|
||||
RegisteredUser ruser = new RegisteredUser(authToken, internalUserId, guestStatus, excludeFromDashboard, leftGuestLobby);
|
||||
RegisteredUser ruser = new RegisteredUser(authToken, internalUserId, guestStatus,
|
||||
excludeFromDashboard, leftGuestLobby, enforceLayout);
|
||||
m.userRegistered(ruser);
|
||||
}
|
||||
}
|
||||
@ -407,7 +413,8 @@ public class MeetingService implements MessageListener {
|
||||
m.getMuteOnStart(), m.getAllowModsToUnmuteUsers(), m.getAllowModsToEjectCameras(), m.getMeetingKeepEvents(),
|
||||
m.breakoutRoomsParams, m.lockSettingsParams, m.getHtml5InstanceId(),
|
||||
m.getGroups(), m.getDisabledFeatures(), m.getNotifyRecordingIsOn(),
|
||||
m.getPresentationUploadExternalDescription(), m.getPresentationUploadExternalUrl());
|
||||
m.getPresentationUploadExternalDescription(), m.getPresentationUploadExternalUrl(),
|
||||
m.getOverrideClientSettings());
|
||||
}
|
||||
|
||||
private String formatPrettyDate(Long timestamp) {
|
||||
@ -422,7 +429,7 @@ public class MeetingService implements MessageListener {
|
||||
gw.registerUser(message.meetingID,
|
||||
message.internalUserId, message.fullname, message.role,
|
||||
message.externUserID, message.authToken, message.sessionToken, message.avatarURL, message.guest,
|
||||
message.authed, message.guestStatus, message.excludeFromDashboard, message.customParameters);
|
||||
message.authed, message.guestStatus, message.excludeFromDashboard, message.enforceLayout, message.customParameters);
|
||||
}
|
||||
|
||||
public Meeting getMeeting(String meetingId) {
|
||||
@ -733,7 +740,7 @@ public class MeetingService implements MessageListener {
|
||||
uploadAuthzTokens.put(message.authzToken, message);
|
||||
}
|
||||
|
||||
private void expirePresentationUploadToken(String usedToken) {
|
||||
public void expirePresentationUploadToken(String usedToken) {
|
||||
uploadAuthzTokens.remove(usedToken);
|
||||
}
|
||||
|
||||
|
@ -19,13 +19,10 @@
|
||||
|
||||
package org.bigbluebutton.api;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLDecoder;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.DecimalFormatSymbols;
|
||||
import java.util.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
@ -37,12 +34,6 @@ import com.google.gson.JsonObject;
|
||||
import org.apache.commons.codec.digest.DigestUtils;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.client.ClientProtocolException;
|
||||
import org.apache.http.client.ResponseHandler;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
import org.bigbluebutton.api.domain.BreakoutRoomsParams;
|
||||
import org.bigbluebutton.api.domain.LockSettingsParams;
|
||||
import org.bigbluebutton.api.domain.Meeting;
|
||||
@ -145,6 +136,7 @@ public class ParamsProcessorUtil {
|
||||
|
||||
private String bbbVersion = "";
|
||||
private Boolean allowRevealOfBBBVersion = false;
|
||||
private Boolean allowOverrideClientSettingsOnCreateCall = false;
|
||||
|
||||
private String formatConfNum(String s) {
|
||||
if (s.length() > 5) {
|
||||
@ -912,6 +904,10 @@ public class ParamsProcessorUtil {
|
||||
return allowRevealOfBBBVersion;
|
||||
}
|
||||
|
||||
public Boolean getAllowOverrideClientSettingsOnCreateCall() {
|
||||
return allowOverrideClientSettingsOnCreateCall;
|
||||
}
|
||||
|
||||
public String processWelcomeMessage(String message, Boolean isBreakout) {
|
||||
String welcomeMessage = message;
|
||||
if (StringUtils.isEmpty(message)) {
|
||||
@ -1156,6 +1152,11 @@ public class ParamsProcessorUtil {
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean parentMeetingExists(String parentMeetingId) {
|
||||
Meeting meeting = ServiceUtils.findMeetingFromMeetingID(parentMeetingId);
|
||||
return meeting != null;
|
||||
}
|
||||
|
||||
/*************************************************
|
||||
* Setters
|
||||
************************************************/
|
||||
@ -1515,4 +1516,8 @@ public class ParamsProcessorUtil {
|
||||
this.allowRevealOfBBBVersion = allowVersion;
|
||||
}
|
||||
|
||||
public void setAllowOverrideClientSettingsOnCreateCall(Boolean allowOverrideClientSettingsOnCreateCall) {
|
||||
this.allowOverrideClientSettingsOnCreateCall = allowOverrideClientSettingsOnCreateCall;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -2,11 +2,14 @@ package org.bigbluebutton.api;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.UUID;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.commons.codec.digest.DigestUtils;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
public final class Util {
|
||||
@ -20,6 +23,14 @@ public final class Util {
|
||||
throw new IllegalStateException("Utility class");
|
||||
}
|
||||
|
||||
public static String extractFilenameFromUrl(String preUploadedPresentation) throws MalformedURLException {
|
||||
URL url = new URL(preUploadedPresentation);
|
||||
String filename = FilenameUtils.getName(url.getPath());
|
||||
String extension = FilenameUtils.getExtension(url.getPath());
|
||||
if (extension == null || extension.isEmpty()) return null;
|
||||
return filename;
|
||||
}
|
||||
|
||||
public static boolean isMeetingIdValidFormat(String id) {
|
||||
Matcher matcher = MEETING_ID_PATTERN.matcher(id);
|
||||
if (matcher.matches()) {
|
||||
@ -51,7 +62,11 @@ public final class Util {
|
||||
}
|
||||
|
||||
public static String createNewFilename(String presId, String fileExt) {
|
||||
if (!fileExt.isEmpty()) {
|
||||
return presId + "." + fileExt;
|
||||
} else {
|
||||
return presId;
|
||||
}
|
||||
}
|
||||
|
||||
public static File createPresentationDir(String meetingId, String presentationDir, String presentationId) {
|
||||
|
@ -118,6 +118,8 @@ public class Meeting {
|
||||
|
||||
private Integer html5InstanceId;
|
||||
|
||||
private String overrideClientSettings = "";
|
||||
|
||||
public Meeting(Meeting.Builder builder) {
|
||||
name = builder.name;
|
||||
extMeetingId = builder.externalId;
|
||||
@ -1131,4 +1133,12 @@ public class Meeting {
|
||||
return new Meeting(this);
|
||||
}
|
||||
}
|
||||
|
||||
public String getOverrideClientSettings() {
|
||||
return overrideClientSettings;
|
||||
}
|
||||
|
||||
public void setOverrideClientSettings(String overrideClientConfigs) {
|
||||
this.overrideClientSettings = overrideClientConfigs;
|
||||
}
|
||||
}
|
||||
|
@ -9,13 +9,16 @@ public class RegisteredUser {
|
||||
private Boolean excludeFromDashboard;
|
||||
private Long guestWaitedOn;
|
||||
private Boolean leftGuestLobby;
|
||||
private String enforceLayout;
|
||||
|
||||
public RegisteredUser(String authToken, String userId, String guestStatus, Boolean excludeFromDashboard, Boolean leftGuestLobby) {
|
||||
public RegisteredUser(String authToken, String userId, String guestStatus, Boolean excludeFromDashboard,
|
||||
Boolean leftGuestLobby, String enforceLayout) {
|
||||
this.authToken = authToken;
|
||||
this.userId = userId;
|
||||
this.guestStatus = guestStatus;
|
||||
this.excludeFromDashboard = excludeFromDashboard;
|
||||
this.leftGuestLobby = leftGuestLobby;
|
||||
this.enforceLayout = enforceLayout;
|
||||
|
||||
Long currentTimeMillis = System.currentTimeMillis();
|
||||
this.registeredOn = currentTimeMillis;
|
||||
@ -30,6 +33,10 @@ public class RegisteredUser {
|
||||
return guestStatus;
|
||||
}
|
||||
|
||||
public void setLeftGuestLobby(boolean bool) {
|
||||
this.leftGuestLobby = bool;
|
||||
}
|
||||
|
||||
public Boolean getLeftGuestLobby() {
|
||||
return leftGuestLobby;
|
||||
}
|
||||
@ -38,6 +45,14 @@ public class RegisteredUser {
|
||||
this.excludeFromDashboard = excludeFromDashboard;
|
||||
}
|
||||
|
||||
public String getEnforceLayout() {
|
||||
return enforceLayout;
|
||||
}
|
||||
|
||||
public void setEnforceLayout(String enforceLayout) {
|
||||
this.enforceLayout = enforceLayout;
|
||||
}
|
||||
|
||||
public Boolean getExcludeFromDashboard() {
|
||||
return excludeFromDashboard;
|
||||
}
|
||||
@ -46,9 +61,6 @@ public class RegisteredUser {
|
||||
this.guestWaitedOn = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public void setLeftGuestLobby(boolean bool) {
|
||||
this.leftGuestLobby = bool;
|
||||
}
|
||||
public Long getGuestWaitedOn() {
|
||||
return this.guestWaitedOn;
|
||||
}
|
||||
|
@ -41,6 +41,7 @@ public class UserSession {
|
||||
public String welcome = null;
|
||||
public String logoutUrl = null;
|
||||
public String defaultLayout = "NOLAYOUT";
|
||||
public String enforceLayout = "";
|
||||
public String avatarURL;
|
||||
public String guestStatus = GuestPolicy.ALLOW;
|
||||
public String clientUrl = null;
|
||||
@ -130,6 +131,10 @@ public class UserSession {
|
||||
return defaultLayout;
|
||||
}
|
||||
|
||||
public String getEnforceLayout() {
|
||||
return enforceLayout;
|
||||
}
|
||||
|
||||
public String getAvatarURL() {
|
||||
return avatarURL;
|
||||
}
|
||||
|
@ -2,13 +2,15 @@ package org.bigbluebutton.api.messaging.messages;
|
||||
|
||||
public class PresentationUploadToken implements IMessage {
|
||||
public final String podId;
|
||||
public final String presentationId;
|
||||
public final String authzToken;
|
||||
public final String filename;
|
||||
public final String meetingId;
|
||||
|
||||
public PresentationUploadToken(String podId, String authzToken, String filename, String meetingId) {
|
||||
public PresentationUploadToken(String podId, String authzToken, String filename, String meetingId, String presentationId) {
|
||||
this.podId = podId;
|
||||
this.authzToken = authzToken;
|
||||
this.presentationId = presentationId;
|
||||
this.filename = filename;
|
||||
this.meetingId = meetingId;
|
||||
}
|
||||
|
@ -18,12 +18,13 @@ public class RegisterUser implements IMessage {
|
||||
public final String guestStatus;
|
||||
public final Boolean excludeFromDashboard;
|
||||
public final Boolean leftGuestLobby;
|
||||
public final String enforceLayout;
|
||||
public final Map<String, String> customParameters;
|
||||
|
||||
public RegisterUser(String meetingID, String internalUserId, String fullname, String role, String externUserID,
|
||||
String authToken, String sessionToken, String avatarURL, Boolean guest,
|
||||
Boolean authed, String guestStatus, Boolean excludeFromDashboard, Boolean leftGuestLobby,
|
||||
Map<String, String> customParameters) {
|
||||
String enforceLayout, Map<String, String> customParameters) {
|
||||
this.meetingID = meetingID;
|
||||
this.internalUserId = internalUserId;
|
||||
this.fullname = fullname;
|
||||
@ -37,6 +38,7 @@ public class RegisterUser implements IMessage {
|
||||
this.guestStatus = guestStatus;
|
||||
this.excludeFromDashboard = excludeFromDashboard;
|
||||
this.leftGuestLobby = leftGuestLobby;
|
||||
this.enforceLayout = enforceLayout;
|
||||
this.customParameters = customParameters;
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user