Merge remote-tracking branch 'bbb/v3.0.x-release' into develop

This commit is contained in:
Anton Georgiev 2024-09-05 12:59:01 -04:00
commit 152ca731e1
1291 changed files with 68001 additions and 75245 deletions

View File

@ -16,21 +16,27 @@ HOW TO WRITE A GOOD PULL REQUEST?
-->
### What does this PR do?
<!-- A brief description of each change being made with this pull request. -->
### Closes Issue(s)
<!-- List here all the issues closed by this pull request. Use keyword `closes` before each issue number
Closes #123456
-->
Closes #
### Motivation
<!-- What inspired you to submit this pull request? -->
### More
### How to test
<!-- List here everything that is necessary for the reviewer to be able to test it completely (docs link, step-by-step, bug cases)
- Is there any specific setup needed, different than the default?
- The linked issue contains all necessary content?
- Have you found any different case that might be tested when you were fixing/implementing it?
-->
### More
<!-- Anything else we should know when reviewing? -->
- [ ] Added/updated documentation

View File

@ -1,9 +1,6 @@
name: Merge branches
on:
workflow_call:
runs:
using: "composite"
using: composite
steps:
- name: Checkout ${{ github.event.pull_request.base.ref || 'master' }}
uses: actions/checkout@v4

View File

@ -0,0 +1,36 @@
name: Upload blob report
description: Merge and upload the blob report to GitHub Actions Artifacts
runs:
using: composite
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- name: Install dependencies
shell: bash
working-directory: ./bigbluebutton-tests/playwright
run: npm ci
- name: Merge artifacts
uses: actions/upload-artifact/merge@v4
with:
name: all-blob-reports
pattern: blob-report-*
delete-merged: true
- name: Download all blob reports from GitHub Actions Artifacts
uses: actions/download-artifact@v4
with:
name: all-blob-reports
path: bigbluebutton-tests/playwright/all-blob-reports
- name: Merge into HTML Report
shell: bash
working-directory: ./bigbluebutton-tests/playwright
run: npx playwright merge-reports --reporter html ./all-blob-reports
- name: Upload HTML tests report
uses: actions/upload-artifact@v4
with:
name: tests-report
overwrite: true
path: |
bigbluebutton-tests/playwright/playwright-report
bigbluebutton-tests/playwright/test-results

View File

@ -5,26 +5,48 @@ on:
- Automated tests
types:
- completed
- requested
env:
isCompleted: ${{ github.event.workflow_run.status == 'completed' }}
jobs:
get-pr-data:
runs-on: ubuntu-latest
concurrency:
group: github-api-request
cancel-in-progress: false
if: ${{ github.event.workflow_run.event == 'pull_request' }}
outputs:
pr-number: ${{ steps.set-env.outputs.pr-number }}
workflow-id: ${{ steps.set-env.outputs.workflow-id }}
pr-number: ${{ steps.pr.outputs.result || steps.set-env.outputs.pr-number }}
workflow-id: ${{ github.event.workflow_run.id || steps.set-env.outputs.workflow-id }}
steps:
# https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#using-data-from-the-triggering-workflow
- name: Download artifact
uses: actions/github-script@v6
- name: Find associated pull request
if: ${{ !fromJson(env.isCompleted) }}
id: pr
uses: actions/github-script@v7
with:
script: |
const response = await github.rest.search.issuesAndPullRequests({
q: 'repo:${{ github.repository }} is:pr sha:${{ github.event.workflow_run.head_sha }}',
per_page: 1,
})
const items = response.data.items
if (items.length < 1) {
console.error('No PRs found')
return
}
const pullRequestNumber = items[0].number
return pullRequestNumber
- name: Download PR artifact
if: ${{ fromJson(env.isCompleted) }}
uses: actions/github-script@v7
with:
script: |
try {
let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: context.payload.workflow_run.id,
});
let matchArtifact = allArtifacts.data.artifacts.filter((artifact) => {
return artifact.name == "pr-comment-data"
})[0];
@ -36,9 +58,14 @@ jobs:
});
let fs = require('fs');
fs.writeFileSync(`${process.env.GITHUB_WORKSPACE}/pr-comment-data.zip`, Buffer.from(download.data));
} catch (error) {
console.log('No artifact found');
}
- name: Unzip artifact
if: ${{ fromJson(env.isCompleted) }}
run: unzip pr-comment-data.zip
- name: Set env variables
- name: Set env variables from artifact
if: ${{ fromJson(env.isCompleted) }}
id: set-env
run: |
echo "pr-number=$(cat ./pr_number)" >> $GITHUB_OUTPUT
@ -50,7 +77,7 @@ jobs:
needs: get-pr-data
steps:
- name: Find Comment
uses: peter-evans/find-comment@v2
uses: peter-evans/find-comment@v3
id: fc
with:
issue-number: ${{ needs.get-pr-data.outputs.pr-number }}
@ -58,7 +85,7 @@ jobs:
body-includes: Automated tests Summary
- name: Remove previous comment
if: steps.fc.outputs.comment-id != ''
uses: actions/github-script@v6
uses: actions/github-script@v7
with:
script: |
github.rest.issues.deleteComment({
@ -66,17 +93,25 @@ jobs:
repo: context.repo.repo,
comment_id: ${{ steps.fc.outputs.comment-id }}
})
- name: In progress tests comment
if: ${{ !fromJson(env.isCompleted) }}
uses: peter-evans/create-or-update-comment@v4
with:
issue-number: ${{ needs.get-pr-data.outputs.pr-number }}
body: |
<h1>Automated tests Summary</h1>
<h3><strong>:hourglass_flowing_sand:</strong> Tests are running...</h3>
- name: Passing tests comment
if: ${{ github.event.workflow_run.conclusion == 'success' }}
uses: peter-evans/create-or-update-comment@v2
uses: peter-evans/create-or-update-comment@v4
with:
issue-number: ${{ needs.get-pr-data.outputs.pr-number }}
body: |
<h1>Automated tests Summary</h1>
<h3><strong>:white_check_mark:</strong> All the CI tests have passed!</h3>
- name: Failing tests comment
if: ${{ github.event.workflow_run.conclusion == 'failure' }}
uses: peter-evans/create-or-update-comment@v2
if: ${{ github.event.workflow_run.conclusion != 'success' && fromJson(env.isCompleted) }}
uses: peter-evans/create-or-update-comment@v4
with:
issue-number: ${{ needs.get-pr-data.outputs.pr-number }}
body: |

View File

@ -8,6 +8,7 @@ on:
paths-ignore:
- "docs/**"
- "**/*.md"
- "bigbluebutton-html5/public/locales/*.json"
pull_request:
types: [opened, synchronize, reopened]
paths-ignore:
@ -63,7 +64,7 @@ jobs:
- package: bbb-fsesl-akka
cache-files-list: akka-bbb-fsesl bbb-common-message
- package: bbb-html5
build-list: bbb-html5-nodejs bbb-html5
build-list: bbb-html5
cache-files-list: bigbluebutton-html5
- package: bbb-freeswitch
build-list: bbb-freeswitch-core bbb-freeswitch-sounds
@ -115,6 +116,7 @@ jobs:
shard: [1, 2, 3, 4, 5, 6, 7, 8]
env:
shard: ${{ matrix.shard }}/8
MATRIX_SHARD_UNDERSCORED: ${{ matrix.shard }}_8
steps:
- uses: actions/checkout@v4
- name: Merge branches
@ -265,10 +267,11 @@ jobs:
command: |
sudo -i <<EOF
set -e
cd /root/ && wget -nv https://raw.githubusercontent.com/bigbluebutton/bbb-install/v3.0.x-release/bbb-install.sh -O bbb-install.sh
cd /root/ && wget -nv https://raw.githubusercontent.com/bigbluebutton/bbb-install/v3.0.x-release--no-mongo/bbb-install.sh -O bbb-install.sh
cat bbb-install.sh | sed "s|> /etc/apt/sources.list.d/bigbluebutton.list||g" | bash -s -- -v jammy-30-dev -s bbb-ci.test -j -d /certs/
bbb-conf --salt bbbci
sed -i "s/\"minify\": true,/\"minify\": false,/" /usr/share/etherpad-lite/settings.json
sudo yq e -i '.log_level = "TRACE"' /usr/share/bbb-graphql-middleware/config.yml
bbb-conf --restart
EOF
- name: List systemctl services
@ -314,7 +317,7 @@ jobs:
find $HOME/.cache/ms-playwright -name libnssckbi.so -exec rm {} \; -exec ln -s /usr/lib/x86_64-linux-gnu/pkcs11/p11-kit-trust.so {} \;
npm run test-firefox-ci -- --shard ${{ env.shard }}
'
- if: always() && github.event_name == 'pull_request'
- if: always()
name: Upload blob report to GitHub Actions Artifacts
uses: actions/upload-artifact@v4
with:
@ -344,17 +347,16 @@ jobs:
chmod a+r -R /home/runner/work/bigbluebutton/bigbluebutton/configs
bbb-conf --zip
ls -t /root/*.tar.gz | head -1 | xargs -I '{}' cp '{}' /home/runner/work/bigbluebutton/bigbluebutton/bbb-logs.tar.gz
echo "MATRIX_SHARD=${{ matrix.shard }}_8" >> $GITHUB_ENV
EOF
- if: failure()
uses: actions/upload-artifact@v4
with:
name: bbb-configs-${{ env.MATRIX_SHARD }}
name: bbb-configs-${{ env.MATRIX_SHARD_UNDERSCORED }}
path: configs
- if: failure()
uses: actions/upload-artifact@v4
with:
name: bbb-logs-${{ env.MATRIX_SHARD }}
name: bbb-logs-${{ env.MATRIX_SHARD_UNDERSCORED }}
path: ./bbb-logs.tar.gz
upload-report:
if: always() && !contains(github.event.head_commit.message, 'Merge pull request')
@ -364,39 +366,14 @@ jobs:
hasReportData: ${{ needs.install-and-run-tests.result == 'success' || needs.install-and-run-tests.result == 'failure' }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18
- name: Install dependencies
- name: Upload blob report
if: ${{ env.hasReportData }}
working-directory: ./bigbluebutton-tests/playwright
run: npm ci
- name: Merge artifacts
uses: actions/upload-artifact/merge@v4
uses: ./.github/actions/upload-blob-report
- name: Remove unnecessary artifact
uses: geekyeggo/delete-artifact@v5
with:
name: all-blob-reports
pattern: blob-report-*
delete-merged: true
- name: Download all blob reports from GitHub Actions Artifacts
if: ${{ env.hasReportData }}
uses: actions/download-artifact@v4
with:
pattern: all-blob-reports-*
path: bigbluebutton-tests/playwright/all-blob-reports
merge-multiple: true
- name: Merge into HTML Report
if: ${{ env.hasReportData }}
working-directory: ./bigbluebutton-tests/playwright
run: npx playwright merge-reports --reporter html ./all-blob-reports
- name: Upload HTML tests report
if: ${{ env.hasReportData }}
uses: actions/upload-artifact@v4
with:
name: tests-report
overwrite: true
path: |
bigbluebutton-tests/playwright/playwright-report
bigbluebutton-tests/playwright/test-results
failOnError: false
- name: Write PR data for auto-comment
if: github.event_name == 'pull_request'
working-directory: ./

View File

@ -14,6 +14,9 @@ jobs:
permissions:
pull-requests: write # for eps1lon/actions-label-merge-conflict to label PRs
runs-on: ubuntu-latest
concurrency:
group: github-api-request
cancel-in-progress: false
steps:
- name: Check for dirty pull requests
uses: eps1lon/actions-label-merge-conflict@v3

View File

@ -28,11 +28,9 @@ jobs:
fetch-depth: 1
- name: Merge branches
uses: ./.github/actions/merge-branches
- name: install meteor
run: curl https://install.meteor.com/ | sh
- name: run meteor npm install
- name: run npm ci
working-directory: bigbluebutton-html5
run: meteor npm install
run: npm ci
- name: typescript code compilation
working-directory: bigbluebutton-html5
run: npx tsc

View File

@ -28,11 +28,9 @@ jobs:
fetch-depth: 1
- name: Merge branches
uses: ./.github/actions/merge-branches
- name: install meteor
run: curl https://install.meteor.com/ | sh
- name: run meteor npm install
- name: install npm dependencies
working-directory: bigbluebutton-html5
run: meteor npm install
run: npm ci
- name: typescript code validation with eslint
working-directory: bigbluebutton-html5
run: npx eslint . --ext .ts,.tsx

1
.gitignore vendored
View File

@ -25,3 +25,4 @@ bbb-presentation-video.zip
bbb-presentation-video
bbb-graphql-actions-adapter-server/
bigbluebutton-html5/public/locales/index.json
node_modules

View File

@ -12,7 +12,7 @@ stages:
# define which docker image to use for builds
default:
image: bigbluebutton/bbb-build:v3.0.x-release--2023-09-26-152524
image: bigbluebutton/bbb-build:v3.0.x-release--2024-08-30-014114
# 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

View File

@ -4,8 +4,10 @@ import org.apache.pekko.http.scaladsl.model._
import org.apache.pekko.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
import org.apache.pekko.http.scaladsl.server.Directives._
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.service.{ HealthzService, MeetingInfoService, PubSubReceiveStatus, PubSubSendStatus, RecordingDBSendStatus }
import org.bigbluebutton.core.api.{ ApiResponseFailure, ApiResponseSuccess, UserInfosApiMsg }
import org.bigbluebutton.service.{ HealthzService, MeetingInfoService, PubSubReceiveStatus, PubSubSendStatus, RecordingDBSendStatus, UserInfoService }
import spray.json._
import scala.concurrent._
import ExecutionContext.Implicits.global
@ -63,7 +65,7 @@ trait JsonSupportProtocolMeetingInfoResponse extends SprayJsonSupport with Defau
implicit val meetingInfoResponseJsonFormat = jsonFormat1(MeetingInfoResponse)
}
class ApiService(healthz: HealthzService, meetingInfoz: MeetingInfoService)
class ApiService(healthz: HealthzService, meetingInfoz: MeetingInfoService, userInfoService: UserInfoService)
extends JsonSupportProtocolHealthResponse
with JsonSupportProtocolMeetingInfoResponse {
@ -124,5 +126,21 @@ class ApiService(healthz: HealthzService, meetingInfoz: MeetingInfoService)
}
complete(entityFuture)
}
} ~
path("userInfo") {
(headerValueByName("x-session-token") & headerValueByName("user-agent")) { (sessionToken, userAgent) =>
get {
val entityFuture = userInfoService.getUserInfo(sessionToken).map {
case ApiResponseSuccess(msg, userInfos: UserInfosApiMsg) =>
val responseMap = userInfoService.generateResponseMap(userInfos)
userInfoService.createHttpResponse(StatusCodes.OK, responseMap)
case ApiResponseFailure(msg, arg) =>
userInfoService.createHttpResponse(StatusCodes.OK, Map("response" -> "unauthorized", "message" -> msg))
}
complete(entityFuture)
}
}
}
}

View File

@ -12,7 +12,7 @@ import org.bigbluebutton.core2.AnalyticsActor
import org.bigbluebutton.core2.FromAkkaAppsMsgSenderActor
import org.bigbluebutton.endpoint.redis.{AppsRedisSubscriberActor, ExportAnnotationsActor, GraphqlConnectionsActor, LearningDashboardActor, RedisRecorderActor}
import org.bigbluebutton.common2.bus.IncomingJsonMessageBus
import org.bigbluebutton.service.{HealthzService, MeetingInfoActor, MeetingInfoService}
import org.bigbluebutton.service.{HealthzService, MeetingInfoActor, MeetingInfoService, UserInfoService}
object Boot extends App with SystemConfiguration {
@ -50,8 +50,6 @@ object Boot extends App with SystemConfiguration {
val meetingInfoService = MeetingInfoService(system, meetingInfoActorRef)
val apiService = new ApiService(healthzService, meetingInfoService)
val redisRecorderActor = system.actorOf(
RedisRecorderActor.props(system, redisConfig, healthzService),
"redisRecorderActor"
@ -95,6 +93,9 @@ object Boot extends App with SystemConfiguration {
val bbbActor = system.actorOf(BigBlueButtonActor.props(system, eventBus, bbbMsgBus, outGW, healthzService), "bigbluebutton-actor")
eventBus.subscribe(bbbActor, meetingManagerChannel)
val userInfoService = UserInfoService(system, bbbActor)
val apiService = new ApiService(healthzService, meetingInfoService, userInfoService)
val redisMessageHandlerActor = system.actorOf(ReceivedJsonMsgHandlerActor.props(bbbMsgBus, incomingJsonMessageBus))
incomingJsonMessageBus.subscribe(redisMessageHandlerActor, toAkkaAppsJsonChannel)

View File

@ -39,7 +39,7 @@ object ClientSettings extends SystemConfiguration {
}
)
//Remove `:private` once it's used only by Meteor internal configs
//Remove `:private` once it's used only by HTML5 client's internal configs
clientSettingsFromFile -= "private"
}
@ -140,26 +140,26 @@ object ClientSettings extends SystemConfiguration {
} yield {
if (dataChannel.contains("name")) {
val channelName = dataChannel("name").toString
val writePermission = {
if (dataChannel.contains("writePermission")) {
dataChannel("writePermission") match {
val pushPermission = {
if (dataChannel.contains("pushPermission")) {
dataChannel("pushPermission") match {
case wPerm: List[String] => wPerm
case _ => {
logger.warn(s"Invalid writePermission for channel $channelName in plugin $pluginName")
logger.warn(s"Invalid pushPermission for channel $channelName in plugin $pluginName")
List()
}
}
} else {
logger.warn(s"Missing config writePermission for channel $channelName in plugin $pluginName")
logger.warn(s"Missing config pushPermission for channel $channelName in plugin $pluginName")
List()
}
}
val deletePermission = {
if (dataChannel.contains("deletePermission")) {
dataChannel("deletePermission") match {
val replaceOrDeletePermission = {
if (dataChannel.contains("replaceOrDeletePermission")) {
dataChannel("replaceOrDeletePermission") match {
case dPerm: List[String] => dPerm
case _ => {
logger.warn(s"Invalid deletePermission for channel $channelName in plugin $pluginName")
logger.warn(s"Invalid replaceOrDeletePermission for channel $channelName in plugin $pluginName")
List()
}
}
@ -168,7 +168,7 @@ object ClientSettings extends SystemConfiguration {
}
}
pluginDataChannels += (channelName -> DataChannel(channelName, writePermission, deletePermission))
pluginDataChannels += (channelName -> DataChannel(channelName, pushPermission, replaceOrDeletePermission))
}
}
case _ => logger.warn(s"Plugin $pluginName has an invalid dataChannels format")
@ -184,7 +184,7 @@ object ClientSettings extends SystemConfiguration {
pluginsFromConfig
}
case class DataChannel(name: String, writePermission: List[String], deletePermission: List[String])
case class DataChannel(name: String, pushPermission: List[String], replaceOrDeletePermission: List[String])
case class Plugin(name: String, url: String, dataChannels: Map[String, DataChannel])
}

View File

@ -1,9 +1,12 @@
package org.bigbluebutton
import org.bigbluebutton.common2.msgs.{ BbbCommonEnvCoreMsg, BbbCoreEnvelope, BbbCoreHeaderWithMeetingId, MessageTypes, MuteUserInVoiceConfSysMsg, MuteUserInVoiceConfSysMsgBody, Routing }
import org.apache.pekko.actor.ActorContext
import org.bigbluebutton.common2.msgs.{ BbbCommonEnvCoreMsg, BbbCoreEnvelope, BbbCoreHeaderWithMeetingId, MessageTypes, Routing }
import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter }
import org.bigbluebutton.core2.{ MeetingStatus2x }
import org.bigbluebutton.core.apps.webcam.CameraHdlrHelpers
import org.bigbluebutton.core.apps.voice.VoiceApp
import org.bigbluebutton.core.models.{
Roles,
Users2x,
@ -16,19 +19,19 @@ import org.bigbluebutton.core.models.{
object LockSettingsUtil {
private def muteUserInVoiceConf(liveMeeting: LiveMeeting, outGW: OutMsgRouter, vu: VoiceUserState, mute: Boolean): Unit = {
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, liveMeeting.props.meetingProp.intId, vu.intId)
val envelope = BbbCoreEnvelope(MuteUserInVoiceConfSysMsg.NAME, routing)
val header = BbbCoreHeaderWithMeetingId(MuteUserInVoiceConfSysMsg.NAME, liveMeeting.props.meetingProp.intId)
val body = MuteUserInVoiceConfSysMsgBody(liveMeeting.props.voiceProp.voiceConf, vu.voiceUserId, mute)
val event = MuteUserInVoiceConfSysMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
outGW.send(msgEvent)
private def muteUserInVoiceConf(
liveMeeting: LiveMeeting,
outGW: OutMsgRouter,
vu: VoiceUserState, mute: Boolean
)(implicit context: ActorContext): Unit = {
VoiceApp.muteUserInVoiceConf(liveMeeting, outGW, vu.intId, mute)
}
private def applyMutingOfUsers(disableMic: Boolean, liveMeeting: LiveMeeting, outGW: OutMsgRouter): Unit = {
private def applyMutingOfUsers(
disableMic: Boolean,
liveMeeting: LiveMeeting,
outGW: OutMsgRouter
)(implicit context: ActorContext): Unit = {
VoiceUsers.findAll(liveMeeting.voiceUsers) foreach { vu =>
Users2x.findWithIntId(liveMeeting.users2x, vu.intId).foreach { user =>
if (user.role == Roles.VIEWER_ROLE && !vu.listenOnly && user.locked) {
@ -44,12 +47,20 @@ object LockSettingsUtil {
}
}
def enforceLockSettingsForAllVoiceUsers(liveMeeting: LiveMeeting, outGW: OutMsgRouter): Unit = {
def enforceLockSettingsForAllVoiceUsers(
liveMeeting: LiveMeeting,
outGW: OutMsgRouter
)(implicit context: ActorContext): Unit = {
val permissions = MeetingStatus2x.getPermissions(liveMeeting.status)
applyMutingOfUsers(permissions.disableMic, liveMeeting, outGW)
}
def enforceLockSettingsForVoiceUser(voiceUser: VoiceUserState, liveMeeting: LiveMeeting, outGW: OutMsgRouter): Unit = {
def enforceLockSettingsForVoiceUser(
voiceUser: VoiceUserState,
liveMeeting: LiveMeeting,
outGW: OutMsgRouter
)(implicit context: ActorContext): Unit = {
val permissions = MeetingStatus2x.getPermissions(liveMeeting.status)
if (permissions.disableMic) {
Users2x.findWithIntId(liveMeeting.users2x, voiceUser.intId).foreach { user =>
@ -65,7 +76,11 @@ object LockSettingsUtil {
}
}
private def enforceListenOnlyUserIsMuted(intUserId: String, liveMeeting: LiveMeeting, outGW: OutMsgRouter): Unit = {
private def enforceListenOnlyUserIsMuted(
intUserId: String,
liveMeeting: LiveMeeting,
outGW: OutMsgRouter
)(implicit context: ActorContext): Unit = {
val voiceUser = VoiceUsers.findWithIntId(liveMeeting.voiceUsers, intUserId)
voiceUser.foreach { vu =>
// Make sure that listen only user is muted. (ralam dec 6, 2019

View File

@ -41,11 +41,16 @@ trait SystemConfiguration {
lazy val voiceConfRecordPath = Try(config.getString("voiceConf.recordPath")).getOrElse("/var/freeswitch/meetings")
lazy val voiceConfRecordCodec = Try(config.getString("voiceConf.recordCodec")).getOrElse("wav")
lazy val voiceConfRecordEnableFileSplitter = Try(config.getBoolean("voiceConf.recordEnableFileSplitter")).getOrElse(false)
lazy val voiceConfRecordFileSplitterIntervalInMinutes = Try(config.getInt("voiceConf.recordFileSplitterIntervalInMinutes")).getOrElse(15)
lazy val checkVoiceRecordingInterval = Try(config.getInt("voiceConf.checkRecordingInterval")).getOrElse(19)
lazy val syncVoiceUsersStatusInterval = Try(config.getInt("voiceConf.syncUserStatusInterval")).getOrElse(43)
lazy val ejectRogueVoiceUsers = Try(config.getBoolean("voiceConf.ejectRogueVoiceUsers")).getOrElse(true)
lazy val dialInApprovalAudioPath = Try(config.getString("voiceConf.dialInApprovalAudioPath")).getOrElse("ivr/ivr-please_hold_while_party_contacted.wav")
lazy val toggleListenOnlyAfterMuteTimer = Try(config.getInt("voiceConf.toggleListenOnlyAfterMuteTimer")).getOrElse(4)
lazy val transparentListenOnlyThreshold = Try(config.getInt("voiceConf.transparentListenOnlyThreshold")).getOrElse(0)
lazy val muteOnStartThreshold = Try(config.getInt("voiceConf.muteOnStartThreshold")).getOrElse(0)
lazy val dialInEnforceGuestPolicy = Try(config.getBoolean("voiceConf.dialInEnforceGuestPolicy")).getOrElse(true)
lazy val recordingChapterBreakLengthInMinutes = Try(config.getInt("recording.chapterBreakLengthInMinutes")).getOrElse(0)
@ -82,7 +87,7 @@ 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"
"/var/bigbluebutton/html5-client/private/config/settings.yml"
)
lazy val clientSettingsPathOverride = Try(config.getString("client.clientSettingsOverrideFilePath")).getOrElse(
"/etc/bigbluebutton/bbb-html5.yml"

View File

@ -15,6 +15,7 @@ import java.util.concurrent.TimeUnit
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.db.{ DatabaseConnection, MeetingDAO }
import org.bigbluebutton.core.domain.MeetingEndReason
import org.bigbluebutton.core.models.Roles
import org.bigbluebutton.core.running.RunningMeeting
import org.bigbluebutton.core.util.ColorPicker
import org.bigbluebutton.core2.RunningMeetings
@ -45,6 +46,8 @@ class BigBlueButtonActor(
private val meetings = new RunningMeetings
private var sessionTokens = new collection.immutable.HashMap[String, (String, String)] //sessionToken -> (meetingId, userId)
override val supervisorStrategy = OneForOneStrategy(maxNrOfRetries = 10, withinTimeRange = 1 minute) {
case e: Exception => {
val sw: StringWriter = new StringWriter()
@ -55,6 +58,14 @@ class BigBlueButtonActor(
}
}
object BBBTasksExecutor
context.system.scheduler.schedule(
1 minute,
1 minute,
self,
BBBTasksExecutor
)
override def preStart() {
bbbMsgBus.subscribe(self, meetingManagerChannel)
DatabaseConnection.initialize()
@ -69,22 +80,61 @@ class BigBlueButtonActor(
def receive = {
// Internal messages
case BBBTasksExecutor => handleMeetingTasksExecutor()
case msg: DestroyMeetingInternalMsg => handleDestroyMeeting(msg)
//Api messages
case msg: GetUserApiMsg => handleGetUserApiMsg(msg, sender)
// 2x messages
case msg: BbbCommonEnvCoreMsg => handleBbbCommonEnvCoreMsg(msg)
case _ => // do nothing
}
private def handleGetUserApiMsg(msg: GetUserApiMsg, actorRef: ActorRef): Unit = {
log.debug("RECEIVED GetUserApiMsg msg {}", msg)
sessionTokens.get(msg.sessionToken) match {
case Some(sessionTokenInfo) =>
RunningMeetings.findWithId(meetings, sessionTokenInfo._1) match {
case Some(m) =>
m.actorRef forward (msg)
case None =>
//The meeting is ended, it will return some data just to confirm the session was valid
//The client can request data after the meeting is ended
val userInfos = Map(
"returncode" -> "SUCCESS",
"sessionToken" -> msg.sessionToken,
"meetingID" -> sessionTokenInfo._1,
"internalUserID" -> sessionTokenInfo._2,
"externMeetingID" -> "",
"externUserID" -> "",
"currentlyInMeeting" -> false,
"authToken" -> "",
"role" -> Roles.VIEWER_ROLE,
"guest" -> "false",
"guestStatus" -> "ALLOWED",
"moderator" -> false,
"presenter" -> false,
"hideViewersCursor" -> false,
"hideViewersAnnotation" -> false,
"hideUserList" -> false,
"webcamsOnlyForModerator" -> false
)
actorRef ! ApiResponseSuccess("Meeting is ended!", UserInfosApiMsg(userInfos))
}
case None =>
actorRef ! ApiResponseFailure("Meeting not found!")
}
}
private def handleBbbCommonEnvCoreMsg(msg: BbbCommonEnvCoreMsg): Unit = {
msg.core match {
case m: CreateMeetingReqMsg => handleCreateMeetingReqMsg(m)
case m: RegisterUserReqMsg => handleRegisterUserReqMsg(m)
case m: GetAllMeetingsReqMsg => handleGetAllMeetingsReqMsg(m)
case m: GetRunningMeetingsReqMsg => handleGetRunningMeetingsReqMsg(m)
case m: CheckAlivePingSysMsg => handleCheckAlivePingSysMsg(m)
case m: ValidateConnAuthTokenSysMsg => handleValidateConnAuthTokenSysMsg(m)
case _: UserGraphqlConnectionEstablishedSysMsg => //Ignore
case _: UserGraphqlConnectionClosedSysMsg => //Ignore
case _: CheckGraphqlMiddlewareAlivePongSysMsg => //Ignore
@ -92,24 +142,16 @@ class BigBlueButtonActor(
}
}
def handleValidateConnAuthTokenSysMsg(msg: ValidateConnAuthTokenSysMsg): Unit = {
RunningMeetings.findWithId(meetings, msg.body.meetingId) match {
case Some(meeting) =>
meeting.actorRef forward msg
case None =>
val event = MsgBuilder.buildValidateConnAuthTokenSysRespMsg(msg.body.meetingId, msg.body.userId,
false, msg.body.connId, msg.body.app)
outGW.send(event)
}
}
def handleRegisterUserReqMsg(msg: RegisterUserReqMsg): Unit = {
log.debug("RECEIVED RegisterUserReqMsg msg {}", msg)
for {
m <- RunningMeetings.findWithId(meetings, msg.header.meetingId)
} yield {
log.debug("FORWARDING Register user message")
//Store sessionTokens and associate them with their respective meetingId + userId owners
sessionTokens += (msg.body.sessionToken -> (msg.body.meetingId, msg.body.intUserId))
m.actorRef forward (msg)
}
}
@ -139,32 +181,18 @@ class BigBlueButtonActor(
}
}
private def handleGetRunningMeetingsReqMsg(msg: GetRunningMeetingsReqMsg): Unit = {
val liveMeetings = RunningMeetings.meetings(meetings)
val meetingIds = liveMeetings.map(m => m.props.meetingProp.intId)
val routing = collection.immutable.HashMap("sender" -> "bbb-apps-akka")
val envelope = BbbCoreEnvelope(GetRunningMeetingsRespMsg.NAME, routing)
val header = BbbCoreBaseHeader(GetRunningMeetingsRespMsg.NAME)
val body = GetRunningMeetingsRespMsgBody(meetingIds)
val event = GetRunningMeetingsRespMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
outGW.send(msgEvent)
}
private def handleGetAllMeetingsReqMsg(msg: GetAllMeetingsReqMsg): Unit = {
RunningMeetings.meetings(meetings).foreach(m => {
m.actorRef ! msg
})
}
private def handleCheckAlivePingSysMsg(msg: CheckAlivePingSysMsg): Unit = {
val event = MsgBuilder.buildCheckAlivePingSysMsg(msg.body.system, msg.body.bbbWebTimestamp, System.currentTimeMillis())
healthzService.sendPubSubStatusMessage(msg.body.akkaAppsTimestamp, System.currentTimeMillis())
outGW.send(event)
}
private def handleMeetingTasksExecutor(): Unit = {
// Delays meeting data for 1 hour post-meeting in case users request info after it ends.
// This routine ensures proper purging if akka-apps restart and the handleDestroyMeeting scheduler does not run.
MeetingDAO.deleteOldMeetings()
}
private def handleDestroyMeeting(msg: DestroyMeetingInternalMsg): Unit = {
for {
@ -194,11 +222,15 @@ class BigBlueButtonActor(
context.stop(m.actorRef)
}
// MeetingDAO.delete(msg.meetingId)
// MeetingDAO.setMeetingEnded(msg.meetingId)
// Removing the meeting is enough, all other tables has "ON DELETE CASCADE"
// UserDAO.softDeleteAllFromMeeting(msg.meetingId)
// MeetingRecordingDAO.updateStopped(msg.meetingId, "")
//Delay removal of session tokens and Graphql data once users might request some info after the meeting is ended
context.system.scheduler.scheduleOnce(Duration.create(60, TimeUnit.MINUTES)) {
log.debug("Removing Graphql data and session tokens. meetingID={}", msg.meetingId)
sessionTokens = sessionTokens.filter(sessionTokenInfo => sessionTokenInfo._2._1 != msg.meetingId)
//In Db, Removing the meeting is enough, all other tables has "ON DELETE CASCADE"
MeetingDAO.delete(msg.meetingId)
}
//Remove ColorPicker idx of the meeting
ColorPicker.reset(m.props.meetingProp.intId)

View File

@ -25,16 +25,7 @@ case class MonitorNumberOfUsersInternalMsg(meetingID: String) extends InMessage
* Audit message sent to meeting to trigger updating clients of meeting time remaining.
* @param meetingId
*/
case class SendTimeRemainingAuditInternalMsg(meetingId: String, timeUpdatedInMinutes: Int) extends InMessage
/**
* Parent message sent to breakout rooms to trigger updating clients of meeting time remaining.
* @param meetingId
* @param timeLeftInSec
*/
case class SendBreakoutTimeRemainingInternalMsg(meetingId: String, timeLeftInSec: Long, timeUpdatedInMinutes: Int) extends InMessage
case class SendRecordingTimerInternalMsg(meetingId: String) extends InMessage
case class MonitorGuestWaitPresenceInternalMsg(meetingId: String) extends InMessage
case class ExtendMeetingDuration(meetingId: String, userId: String) extends InMessage
case class DestroyMeetingInternalMsg(meetingId: String) extends InMessage
@ -130,4 +121,14 @@ case class UserClosedAllGraphqlConnectionsInternalMsg(userId: String) extends In
* Sent by GraphqlActionsActor to inform MeetingActor that user came back from disconnection
* @param userId
*/
case class UserEstablishedGraphqlConnectionInternalMsg(userId: String) extends InMessage
case class UserEstablishedGraphqlConnectionInternalMsg(userId: String, clientType: String, isMobile: Boolean) extends InMessage
/**
* API endpoint /userInfo to provide User Session Variables messages
*/
case class GetUserApiMsg(sessionToken: String)
case class UserInfosApiMsg(infos: Map[String, Any])
trait ApiResponse
case class ApiResponseSuccess(msg: String, any: Any = null) extends ApiResponse
case class ApiResponseFailure(msg: String, any: Any = null) extends ApiResponse

View File

@ -18,8 +18,11 @@ object BreakoutModel {
captureSlides: Boolean,
captureNotesFilename: String,
captureSlidesFilename: String,
allPages: Boolean,
presId: String,
): BreakoutRoom2x = {
new BreakoutRoom2x(id, externalId, name, parentId, sequence, shortName, isDefaultName, freeJoin, voiceConf, assignedUsers, Vector(), Vector(), None, false, captureNotes, captureSlides, captureNotesFilename, captureSlidesFilename)
new BreakoutRoom2x(id, externalId, name, parentId, sequence, shortName, isDefaultName, freeJoin, voiceConf, assignedUsers, Vector(), Vector(), None, false,
captureNotes, captureSlides, captureNotesFilename, captureSlidesFilename, allPages, presId)
}
}

View File

@ -20,32 +20,6 @@ class CaptionModel {
return None
}
def updateTranscriptOwner(name: String, locale: String, ownerId: String): Map[String, TranscriptVO] = {
var updatedTranscripts = new HashMap[String, TranscriptVO]
// clear owner from previous locale
if (ownerId.length > 0) {
findTranscriptByOwnerId(ownerId).foreach(t => {
val oldTranscript = t._2.copy(ownerId = "")
transcripts += t._1 -> oldTranscript
updatedTranscripts += t._1 -> oldTranscript
})
}
// change the owner if it does exist
if (transcripts contains name) {
val newTranscript = transcripts(name).copy(ownerId = ownerId)
transcripts += name -> newTranscript
updatedTranscripts += name -> newTranscript
} else { // create the locale if it doesn't exist
val addedTranscript = createTranscript(name, locale, ownerId)
updatedTranscripts += name -> addedTranscript
}
updatedTranscripts
}
def getHistory(): Map[String, TranscriptVO] = {
transcripts
}
@ -97,20 +71,6 @@ class CaptionModel {
locale
}
def checkCaptionOwnerLogOut(userId: String): Option[(String, TranscriptVO)] = {
var rtnTranscript: Option[(String, TranscriptVO)] = None
if (userId.length > 0) {
findTranscriptByOwnerId(userId).foreach(t => {
val oldTranscript = t._2.copy(ownerId = "")
transcripts += t._1 -> oldTranscript
rtnTranscript = Some((t._1, oldTranscript))
})
}
rtnTranscript
}
def isUserCaptionOwner(userId: String, name: String): Boolean = {
var isOwner: Boolean = false;

View File

@ -5,7 +5,6 @@ import org.bigbluebutton.core2.message.handlers.guests._
trait GuestsApp extends GetGuestsWaitingApprovalReqMsgHdlr
with GuestsWaitingApprovedMsgHdlr
with GuestWaitingLeftMsgHdlr
with UpdatePositionInWaitingQueueReqMsgHdlr
with SetGuestPolicyMsgHdlr
with SetGuestLobbyMessageMsgHdlr

View File

@ -88,9 +88,6 @@ object PermissionCheck extends SystemConfiguration {
UsersApp.ejectUserFromMeeting(outGW, liveMeeting, userId, ejectedBy, reason, EjectReasonCode.PERMISSION_FAILED, ban = false)
// send a system message to force disconnection
Sender.sendDisconnectClientSysMsg(meetingId, userId, ejectedBy, reason, outGW)
// Force reconnection with graphql to refresh permissions
for {
regUser <- RegisteredUsers.findWithUserId(userId, liveMeeting.registeredUsers)

View File

@ -6,7 +6,7 @@ object TimerModel {
stopwatch: Boolean = true,
time: Int = 0,
accumulated: Int = 0,
track: String = "",
track: String = "noTrack",
): Unit = {
model.stopwatch = stopwatch
model.time = time
@ -17,7 +17,6 @@ object TimerModel {
def reset(model: TimerModel) : Unit = {
model.accumulated = 0
model.startedAt = if (model.running) System.currentTimeMillis() else 0
model.endedAt = 0
}
def setIsActive(model: TimerModel, active: Boolean): Unit = {
@ -45,18 +44,39 @@ object TimerModel {
}
def setRunning(model: TimerModel, running: Boolean): Unit = {
resetTimerIfFinished(model)
//If it is running and will stop, calculate new Accumulated
if(getRunning(model) && !running) {
val now = System.currentTimeMillis()
// If the timer is running and will stop, update accumulated time
if (isRunning(model) && !running) {
val accumulated = getAccumulated(model) + Math.abs(now - getStartedAt(model)).toInt
this.setAccumulated(model, accumulated)
setAccumulated(model, accumulated)
}
// If the timer is not running and will start, set the start time
if (!isRunning(model) && running) {
setStartedAt(model, now)
}
// Update the running status of the model
model.running = running
}
def getRunning(model: TimerModel): Boolean = {
def resetTimerIfFinished(model: TimerModel) = {
// If the timer is finished, reset the accumulated time and start time if running
if (isRunning(model)
&& !isStopwatch(model)
&& (model.startedAt + (model.time - model.accumulated)) < System.currentTimeMillis()) {
model.running = false
reset(model)
true
} else {
false
}
}
def isRunning(model: TimerModel): Boolean = {
model.running
}
@ -64,7 +84,7 @@ object TimerModel {
model.stopwatch = stopwatch
}
def getStopwatch(model: TimerModel): Boolean = {
def isStopwatch(model: TimerModel): Boolean = {
model.stopwatch
}
@ -83,23 +103,14 @@ object TimerModel {
def getTime(model: TimerModel): Int = {
model.time
}
def setEndedAt(model: TimerModel, timestamp: Long): Unit = {
model.endedAt = timestamp
}
def getEndedAt(model: TimerModel): Long = {
model.endedAt
}
}
class TimerModel {
private var startedAt: Long = 0
private var endedAt: Long = 0
private var accumulated: Int = 0
private var running: Boolean = false
private var time: Int = 0
private var stopwatch: Boolean = true
private var track: String = ""
private var track: String = "noTrack"
private var isActive: Boolean = false
}

View File

@ -54,11 +54,21 @@ class WhiteboardModel extends SystemConfiguration {
for (annotation <- annotations) {
val oldAnnotation = wb.annotationsMap.get(annotation.id)
if (!oldAnnotation.isEmpty) {
if (oldAnnotation.isDefined) {
val hasPermission = isPresenter || isModerator || oldAnnotation.get.userId == userId
if (hasPermission) {
// Merge old and new annotation properties
val mergedAnnotationInfo = deepMerge(oldAnnotation.get.annotationInfo, annotation.annotationInfo)
// Determine if the annotation is a line shape
val isLineShape = annotation.annotationInfo.get("type").contains("line")
// Merge old and new annotation properties with special handling for line shape
val mergedAnnotationInfo = if (isLineShape) {
val newProps = annotation.annotationInfo.get("props").asInstanceOf[Option[Map[String, Any]]].getOrElse(Map.empty)
val oldProps = oldAnnotation.get.annotationInfo.get("props").asInstanceOf[Option[Map[String, Any]]].getOrElse(Map.empty)
val updatedProps = overwriteLineShapeHandles(oldProps, newProps)
annotation.annotationInfo + ("props" -> updatedProps)
} else {
deepMerge(oldAnnotation.get.annotationInfo, annotation.annotationInfo)
}
// Apply cleaning if it's an arrow annotation
val finalAnnotationInfo = if (annotation.annotationInfo.get("type").contains("arrow")) {
@ -90,6 +100,15 @@ class WhiteboardModel extends SystemConfiguration {
annotationsAdded
}
private def overwriteLineShapeHandles(oldProps: Map[String, Any], newProps: Map[String, Any]): Map[String, Any] = {
val newHandles = newProps.get("handles")
val updatedProps = oldProps ++ newProps.filter {
case ("handles", _) => false // Remove the old handles
case _ => true
}
updatedProps ++ newHandles.map("handles" -> _)
}
private def cleanArrowAnnotationProps(annotationInfo: Map[String, _]): Map[String, _] = {
annotationInfo.get("props") match {
case Some(props: Map[String, _]) =>

View File

@ -4,7 +4,7 @@ import org.bigbluebutton.ClientSettings.getConfigPropertyValueByPathAsStringOrEl
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.bus.MessageBus
import org.bigbluebutton.core.db.CaptionDAO
import org.bigbluebutton.core.models.{AudioCaptions, UserState, Users2x}
import org.bigbluebutton.core.models.{AudioCaptions, UserState, Pads, Users2x}
import org.bigbluebutton.core.running.LiveMeeting
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
@ -26,12 +26,12 @@ trait UpdateTranscriptPubMsgHdlr {
bus.outGW.send(msgEvent)
}
def sendPadUpdatePubMsg(userId: String, defaultPad: String, text: String, transcript: Boolean): Unit = {
val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, meetingId, "nodeJSapp")
val envelope = BbbCoreEnvelope(PadUpdatePubMsg.NAME, routing)
val header = BbbClientMsgHeader(PadUpdatePubMsg.NAME, meetingId, userId)
val body = PadUpdatePubMsgBody(defaultPad, text, transcript)
val event = PadUpdatePubMsg(header, body)
def sendPadUpdateCmdMsg(groupId: String, defaultPad: String, text: String, transcript: Boolean): Unit = {
val routing = collection.immutable.HashMap("sender" -> "bbb-apps-akka")
val envelope = BbbCoreEnvelope(PadUpdateCmdMsg.NAME, routing)
val header = BbbCoreHeaderWithMeetingId(PadUpdateCmdMsg.NAME, liveMeeting.props.meetingProp.intId)
val body = PadUpdateCmdMsgBody(groupId, defaultPad, text)
val event = PadUpdateCmdMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
bus.outGW.send(msgEvent)
@ -82,7 +82,7 @@ trait UpdateTranscriptPubMsgHdlr {
for {
u <- Users2x.findWithIntId(liveMeeting.users2x, msg.header.userId)
} yield {
CaptionDAO.insertOrUpdateAudioCaption(msg.body.transcriptId, meetingId, msg.header.userId, transcript, u.speechLocale)
CaptionDAO.insertOrUpdateCaption(msg.body.transcriptId, meetingId, msg.header.userId, transcript, u.speechLocale)
}
broadcastEvent(
@ -111,7 +111,10 @@ trait UpdateTranscriptPubMsgHdlr {
alternativeValue = ""
)
sendPadUpdatePubMsg(msg.header.userId, defaultPad, userSpoke, transcript = true)
Pads.getGroup(liveMeeting.pads, defaultPad) match {
case Some(group) => sendPadUpdateCmdMsg(group.groupId, defaultPad, userSpoke, transcript = true)
case _ =>
}
}
}

View File

@ -16,6 +16,7 @@ trait BreakoutApp2x extends BreakoutRoomCreatedMsgHdlr
with SendMessageToAllBreakoutRoomsMsgHdlr
with SendMessageToBreakoutRoomInternalMsgHdlr
with RequestBreakoutJoinURLReqMsgHdlr
with SetBreakoutRoomInviteDismissedReqMsgHdlr
with SendBreakoutUsersUpdateMsgHdlr
with TransferUserToMeetingRequestHdlr
with EndBreakoutRoomInternalMsgHdlr

View File

@ -1,9 +1,8 @@
package org.bigbluebutton.core.apps.breakout
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.api.BreakoutRoomUsersUpdateInternalMsg
import org.bigbluebutton.core.db.{ BreakoutRoomUserDAO, UserBreakoutRoomDAO }
import org.bigbluebutton.core.domain.{ BreakoutRoom2x, MeetingState2x }
import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.models.{ RegisteredUsers, Users2x }
import org.bigbluebutton.core.running.{ MeetingActor, OutMsgRouter }
@ -14,24 +13,11 @@ trait BreakoutRoomUsersUpdateMsgHdlr {
def handleBreakoutRoomUsersUpdateInternalMsg(msg: BreakoutRoomUsersUpdateInternalMsg, state: MeetingState2x): MeetingState2x = {
def broadcastEvent(room: BreakoutRoom2x): BbbCommonEnvCoreMsg = {
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, props.meetingProp.intId, "not-used")
val envelope = BbbCoreEnvelope(UpdateBreakoutUsersEvtMsg.NAME, routing)
val header = BbbClientMsgHeader(UpdateBreakoutUsersEvtMsg.NAME, props.meetingProp.intId, "not-used")
val users = room.users.map(u => BreakoutUserVO(u.id, u.name))
val body = UpdateBreakoutUsersEvtMsgBody(props.meetingProp.intId, msg.breakoutId, users)
val event = UpdateBreakoutUsersEvtMsg(header, body)
BbbCommonEnvCoreMsg(envelope, event)
}
val breakoutModel = for {
model <- state.breakout
room <- model.find(msg.breakoutId)
} yield {
val updatedRoom = room.copy(users = msg.users, voiceUsers = msg.voiceUsers)
val msgEvent = broadcastEvent(updatedRoom)
outGW.send(msgEvent)
//Update user lastActivityTime in parent room (to avoid be ejected while is in Breakout room)
for {

View File

@ -38,6 +38,9 @@ trait ChangeUserBreakoutReqMsgHdlr extends RightsManagementTrait {
})
}
val isSameRoom = msg.body.fromBreakoutId == msg.body.toBreakoutId
val removePreviousRoomFromDb = !breakoutModel.rooms.exists(r => r._2.freeJoin) && !isSameRoom
//Get join URL for room To
val redirectToHtml5JoinURL = (
for {
@ -46,7 +49,6 @@ trait ChangeUserBreakoutReqMsgHdlr extends RightsManagementTrait {
} yield redirectToHtml5JoinURL
).getOrElse("")
BreakoutHdlrHelpers.sendChangeUserBreakoutMsg(
outGW,
meetingId,
@ -57,8 +59,13 @@ trait ChangeUserBreakoutReqMsgHdlr extends RightsManagementTrait {
)
//Update database
BreakoutRoomUserDAO.updateRoomChanged(meetingId, msg.body.userId, msg.body.fromBreakoutId, msg.body.toBreakoutId, redirectToHtml5JoinURL)
BreakoutRoomUserDAO.updateRoomChanged(
meetingId,
msg.body.userId,
msg.body.fromBreakoutId,
msg.body.toBreakoutId,
redirectToHtml5JoinURL,
removePreviousRoomFromDb)
//Send notification to moved User
for {

View File

@ -55,26 +55,30 @@ trait CreateBreakoutRoomsCmdMsgHdlr extends RightsManagementTrait {
}
def processRequest(msg: CreateBreakoutRoomsCmdMsg, state: MeetingState2x): MeetingState2x = {
val presId = getPresentationId(state)
val presSlide = getPresentationSlide(state)
val presId = getPresentationId(state) // The current presentation
val presSlide = getPresentationSlide(state) // The current slide
val parentId = liveMeeting.props.meetingProp.intId
var rooms = new collection.immutable.HashMap[String, BreakoutRoom2x]
var i = 0
for (room <- msg.body.rooms) {
val roomPresId = if (room.presId.isEmpty) presId else room.presId;
i += 1
val (internalId, externalId) = BreakoutRoomsUtil.createMeetingIds(liveMeeting.props.meetingProp.intId, i)
val voiceConf = BreakoutRoomsUtil.createVoiceConfId(liveMeeting.props.voiceProp.voiceConf, i)
val breakout = BreakoutModel.create(parentId, internalId, externalId, room.name, room.sequence, room.shortName,
room.isDefaultName, room.freeJoin, voiceConf, room.users, msg.body.captureNotes,
msg.body.captureSlides, room.captureNotesFilename, room.captureSlidesFilename)
msg.body.captureSlides, room.captureNotesFilename, room.captureSlidesFilename,
room.allPages, roomPresId)
rooms = rooms + (breakout.id -> breakout)
}
for (breakout <- rooms.values.toVector) {
val roomSlides = if (breakout.allPages) -1 else presSlide;
val roomDetail = new BreakoutRoomDetail(
breakout.id, breakout.name,
liveMeeting.props.meetingProp.intId,
@ -87,7 +91,9 @@ trait CreateBreakoutRoomsCmdMsgHdlr extends RightsManagementTrait {
msg.body.durationInMinutes,
liveMeeting.props.password.moderatorPass,
liveMeeting.props.password.viewerPass,
presId, presSlide, msg.body.record,
breakout.presId,
roomSlides,
msg.body.record,
liveMeeting.props.breakoutProps.privateChatEnabled,
breakout.captureNotes,
breakout.captureSlides,

View File

@ -32,9 +32,6 @@ trait EjectUserFromBreakoutInternalMsgHdlr {
//TODO inform reason
UserDAO.softDelete(registeredUser.meetingId, registeredUser.id)
// send a system message to force disconnection
Sender.sendDisconnectClientSysMsg(msg.breakoutId, registeredUser.id, msg.ejectedBy, msg.reasonCode, outGW)
// Force reconnection with graphql to refresh permissions
Sender.sendForceUserGraphqlReconnectionSysMsg(liveMeeting.props.meetingProp.intId, registeredUser.id, registeredUser.sessionToken, msg.reasonCode, outGW)

View File

@ -1,10 +1,11 @@
package org.bigbluebutton.core.apps.breakout
import org.bigbluebutton.common2.msgs.{ BbbClientMsgHeader, BbbCommonEnvCoreMsg, BbbCoreEnvelope, BbbCoreHeaderWithMeetingId, ExportJob, MessageTypes, PresentationConversionUpdateEvtMsg, PresentationConversionUpdateEvtMsgBody, PresentationConversionUpdateSysPubMsg, PresentationUploadTokenSysPubMsg, PresentationUploadTokenSysPubMsgBody, Routing, StoreExportJobInRedisSysMsg, StoreExportJobInRedisSysMsgBody }
import org.bigbluebutton.common2.msgs.{ BbbClientMsgHeader, BbbCommonEnvCoreMsg, BbbCoreEnvelope, BbbCoreHeaderWithMeetingId, ExportJob, MessageTypes, PresentationConversionUpdateEvtMsg, PresentationConversionUpdateEvtMsgBody, PresentationConversionUpdateSysPubMsg, PresentationPageForExport, PresentationUploadTokenSysPubMsg, PresentationUploadTokenSysPubMsgBody, Routing, StoreExportJobInRedisSysMsg, StoreExportJobInRedisSysMsgBody, StoredAnnotations }
import org.bigbluebutton.core.api.{ CapturePresentationReqInternalMsg, EndBreakoutRoomInternalMsg }
import org.bigbluebutton.core.apps.presentationpod.PresentationPodsApp
import org.bigbluebutton.core.bus.{ BigBlueButtonEvent, InternalEventBus }
import org.bigbluebutton.core.models.Pads
import org.bigbluebutton.core.db.{ PresPresentationDAO }
import org.bigbluebutton.core.models.{ Pads, PresentationInPod, PresentationPage, PresentationPod }
import org.bigbluebutton.core.running.{ BaseMeetingActor, HandlerHelpers, LiveMeeting, OutMsgRouter }
trait EndBreakoutRoomInternalMsgHdlr extends HandlerHelpers {
@ -22,6 +23,14 @@ trait EndBreakoutRoomInternalMsgHdlr extends HandlerHelpers {
}
if (liveMeeting.props.breakoutProps.captureNotes) {
handleCaptureNotes(msg)
}
log.info("Breakout room {} ended by parent meeting {}.", msg.breakoutId, msg.parentId)
sendEndMeetingDueToExpiry(msg.reason, eventBus, outGW, liveMeeting, "system")
}
def handleCaptureNotes(msg: EndBreakoutRoomInternalMsg) {
for {
group <- Pads.getGroup(liveMeeting.pads, "notes")
} yield {
@ -30,6 +39,10 @@ trait EndBreakoutRoomInternalMsgHdlr extends HandlerHelpers {
val jobId: String = s"${msg.breakoutId}-notes" // Used as the temporaryPresentationId upon upload
val presentationId = PresentationPodsApp.generatePresentationId(filename)
var pres = new PresentationInPod(presentationId, default = false, current = false, name = filename,
pages = Map.empty, downloadable = false, downloadFileExtension = "", removable = true, filenameConverted = filename,
uploadCompleted = false, numPages = 0, errorMsgKey = "", errorDetails = Map.empty)
if (group.rev > 0) {
//Request upload of the sharedNotes of breakoutRoom
val presentationUploadToken: String = PresentationPodsApp.generateToken("DEFAULT_PRESENTATION_POD", userId)
@ -39,15 +52,15 @@ trait EndBreakoutRoomInternalMsgHdlr extends HandlerHelpers {
val job = buildStoreExportJobInRedisSysMsg(exportJob, liveMeeting)
outGW.send(job)
} else {
// Notify that no content is available
pres = pres.copy(errorMsgKey = "204")
val event = buildPresentationConversionUpdateEvtMsg(msg.parentId, presentationId, filename, jobId)
outGW.send(event)
}
}
}
log.info("Breakout room {} ended by parent meeting {}.", msg.breakoutId, msg.parentId)
sendEndMeetingDueToExpiry(msg.reason, eventBus, outGW, liveMeeting, "system")
PresPresentationDAO.updateConversionStarted(msg.parentId, pres)
}
}
def buildStoreExportJobInRedisSysMsg(exportJob: ExportJob, liveMeeting: LiveMeeting): BbbCommonEnvCoreMsg = {

View File

@ -1,15 +0,0 @@
package org.bigbluebutton.core.apps.breakout
import org.bigbluebutton.core.api.SendBreakoutTimeRemainingInternalMsg
import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter }
import org.bigbluebutton.core2.message.senders.MsgBuilder
trait SendBreakoutTimeRemainingInternalMsgHdlr {
val liveMeeting: LiveMeeting
val outGW: OutMsgRouter
def handleSendBreakoutTimeRemainingInternalMsg(msg: SendBreakoutTimeRemainingInternalMsg): Unit = {
val event = MsgBuilder.buildMeetingTimeRemainingUpdateEvtMsg(liveMeeting.props.meetingProp.intId, msg.timeLeftInSec.toInt, msg.timeUpdatedInMinutes)
outGW.send(event)
}
}

View File

@ -0,0 +1,22 @@
package org.bigbluebutton.core.apps.breakout
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
import org.bigbluebutton.core.db.BreakoutRoomUserDAO
import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.models.{ Roles, Users2x }
import org.bigbluebutton.core.running.{ MeetingActor, OutMsgRouter }
trait SetBreakoutRoomInviteDismissedReqMsgHdlr extends RightsManagementTrait {
this: MeetingActor =>
val outGW: OutMsgRouter
def handleSetBreakoutRoomInviteDismissedReqMsg(msg: SetBreakoutRoomInviteDismissedReqMsg) = {
for {
requesterUser <- Users2x.findWithIntId(liveMeeting.users2x, msg.header.userId)
} yield {
BreakoutRoomUserDAO.updateInviteDismissedAt(requesterUser.meetingId, requesterUser.intId)
}
}
}

View File

@ -1,7 +1,7 @@
package org.bigbluebutton.core.apps.breakout
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.api.{ SendTimeRemainingAuditInternalMsg, UpdateBreakoutRoomTimeInternalMsg }
import org.bigbluebutton.core.api.UpdateBreakoutRoomTimeInternalMsg
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
import org.bigbluebutton.core.bus.BigBlueButtonEvent
import org.bigbluebutton.core.db.{ BreakoutRoomDAO, MeetingDAO, NotificationDAO }
@ -88,9 +88,6 @@ trait UpdateBreakoutRoomsTimeMsgHdlr extends RightsManagementTrait {
val event = buildUpdateBreakoutRoomsTimeEvtMsg(msg.body.timeInMinutes)
outGW.send(event)
//Force Update time remaining in the clients
eventBus.publish(BigBlueButtonEvent(props.meetingProp.intId, SendTimeRemainingAuditInternalMsg(props.meetingProp.intId, msg.body.timeInMinutes)))
updatedModel match {
case Some(model) => {
state.update(Some(model))

View File

@ -6,7 +6,7 @@ import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.bus.MessageBus
import org.bigbluebutton.core.running.LiveMeeting
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
import org.bigbluebutton.core.db.{ CaptionLocaleDAO, CaptionTypes }
import org.bigbluebutton.core.db.{ CaptionDAO, CaptionLocaleDAO, CaptionTypes }
class CaptionApp2x(implicit val context: ActorContext) extends RightsManagementTrait {
val log = Logging(context.system, getClass)
@ -15,18 +15,10 @@ class CaptionApp2x(implicit val context: ActorContext) extends RightsManagementT
liveMeeting.captionModel.getHistory()
}
def updateCaptionOwner(liveMeeting: LiveMeeting, name: String, locale: String, userId: String): Map[String, TranscriptVO] = {
liveMeeting.captionModel.updateTranscriptOwner(name, locale, userId)
}
def editCaptionHistory(liveMeeting: LiveMeeting, userId: String, startIndex: Integer, endIndex: Integer, name: String, text: String): Boolean = {
liveMeeting.captionModel.editHistory(userId, startIndex, endIndex, name, text)
}
def checkCaptionOwnerLogOut(liveMeeting: LiveMeeting, userId: String): Option[(String, TranscriptVO)] = {
liveMeeting.captionModel.checkCaptionOwnerLogOut(userId)
}
def isUserCaptionOwner(liveMeeting: LiveMeeting, userId: String, name: String): Boolean = {
liveMeeting.captionModel.isUserCaptionOwner(userId, name)
}
@ -59,7 +51,23 @@ class CaptionApp2x(implicit val context: ActorContext) extends RightsManagementT
}
}
}
def handle(msg: CaptionSubmitTranscriptPubMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
val meetingId = liveMeeting.props.meetingProp.intId
def broadcastSuccessEvent(transcriptId: String, transcript: String, locale: String): Unit = {
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, liveMeeting.props.meetingProp.intId, msg.header.userId)
val envelope = BbbCoreEnvelope(CaptionSubmitTranscriptEvtMsg.NAME, routing)
val header = BbbClientMsgHeader(CaptionSubmitTranscriptEvtMsg.NAME, liveMeeting.props.meetingProp.intId, msg.header.userId)
val body = CaptionSubmitTranscriptEvtMsgBody(transcriptId, transcript, locale, msg.body.captionType)
val event = CaptionSubmitTranscriptEvtMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
bus.outGW.send(msgEvent)
}
CaptionDAO.insertOrUpdateCaption(msg.body.transcriptId, meetingId, msg.header.userId,
msg.body.transcript, msg.body.locale, msg.body.captionType)
broadcastSuccessEvent(msg.body.transcriptId, msg.body.transcript, msg.body.locale)
}
def handle(msg: SendCaptionHistoryReqMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
def broadcastEvent(msg: SendCaptionHistoryReqMsg, history: Map[String, TranscriptVO]): Unit = {
val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, liveMeeting.props.meetingProp.intId, msg.header.userId)
@ -75,46 +83,25 @@ class CaptionApp2x(implicit val context: ActorContext) extends RightsManagementT
broadcastEvent(msg, getCaptionHistory(liveMeeting))
}
def handle(msg: UpdateCaptionOwnerPubMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
def broadcastUpdateCaptionOwnerEvent(name: String, locale: String, newOwnerId: String): Unit = {
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, liveMeeting.props.meetingProp.intId, newOwnerId)
val envelope = BbbCoreEnvelope(UpdateCaptionOwnerEvtMsg.NAME, routing)
val header = BbbClientMsgHeader(UpdateCaptionOwnerEvtMsg.NAME, liveMeeting.props.meetingProp.intId, newOwnerId)
def handle(msg: AddCaptionLocalePubMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
def broadcastAddCaptionLocaleEvent(locale: String, userId: String): Unit = {
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, liveMeeting.props.meetingProp.intId, userId)
val envelope = BbbCoreEnvelope(AddCaptionLocaleEvtMsg.NAME, routing)
val header = BbbClientMsgHeader(AddCaptionLocaleEvtMsg.NAME, liveMeeting.props.meetingProp.intId, userId)
val body = UpdateCaptionOwnerEvtMsgBody(name, locale, newOwnerId)
val event = UpdateCaptionOwnerEvtMsg(header, body)
val body = AddCaptionLocaleEvtMsgBody(locale)
val event = AddCaptionLocaleEvtMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
bus.outGW.send(msgEvent)
CaptionLocaleDAO.insertOrUpdateCaptionLocale(liveMeeting.props.meetingProp.intId, locale, CaptionTypes.TYPED, newOwnerId)
CaptionLocaleDAO.insertOrUpdateCaptionLocale(liveMeeting.props.meetingProp.intId, locale, CaptionTypes.TYPED, userId)
}
if (permissionFailed(PermissionCheck.MOD_LEVEL, PermissionCheck.VIEWER_LEVEL, liveMeeting.users2x, msg.header.userId)) {
val meetingId = liveMeeting.props.meetingProp.intId
val reason = "No permission to change caption owners."
val reason = "No permission to add caption locale."
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting)
} else {
updateCaptionOwner(liveMeeting, msg.body.name, msg.body.locale, msg.body.ownerId).foreach(f => {
broadcastUpdateCaptionOwnerEvent(f._1, f._2.locale, f._2.ownerId)
})
}
}
def handleUserLeavingMsg(userId: String, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
def broadcastUpdateCaptionOwnerEvent(name: String, locale: String, newOwnerId: String): Unit = {
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, liveMeeting.props.meetingProp.intId, newOwnerId)
val envelope = BbbCoreEnvelope(UpdateCaptionOwnerEvtMsg.NAME, routing)
val header = BbbClientMsgHeader(UpdateCaptionOwnerEvtMsg.NAME, liveMeeting.props.meetingProp.intId, newOwnerId)
val body = UpdateCaptionOwnerEvtMsgBody(name, locale, newOwnerId)
val event = UpdateCaptionOwnerEvtMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
bus.outGW.send(msgEvent)
}
for {
transcriptInfo <- checkCaptionOwnerLogOut(liveMeeting, userId)
} yield {
broadcastUpdateCaptionOwnerEvent(transcriptInfo._1, transcriptInfo._2.locale, transcriptInfo._2.ownerId)
broadcastAddCaptionLocaleEvent(msg.body.locale, msg.header.userId)
}
}
}

View File

@ -53,7 +53,7 @@ trait CreateGroupChatReqMsgHdlr extends SystemConfiguration {
} else {
GroupChatApp.getGroupChatOfUsers(msg.header.userId, msg.body.users, state) match {
case Some(groupChat) =>
ChatUserDAO.updateChatVisible(msg.header.meetingId, groupChat.id, msg.header.userId)
ChatUserDAO.updateChatVisible(msg.header.meetingId, groupChat.id, msg.header.userId, visible = true)
state
case None =>
val newState = for {

View File

@ -20,10 +20,10 @@ object GroupChatApp {
GroupChatFactory.create(gcId, access, createBy, users, msgs)
}
def toGroupChatMessage(sender: GroupChatUser, msg: GroupChatMsgFromUser, emphasizedText: Boolean): GroupChatMessage = {
def toGroupChatMessage(sender: GroupChatUser, msg: GroupChatMsgFromUser, emphasizedText: Boolean, metadata: Map[String, Any] = Map.empty): GroupChatMessage = {
val now = System.currentTimeMillis()
val id = GroupChatFactory.genId()
GroupChatMessage(id, now, msg.correlationId, now, now, sender, emphasizedText, msg.message)
GroupChatMessage(id, now, msg.correlationId, now, now, sender, emphasizedText, msg.message, metadata)
}
def toMessageToUser(msg: GroupChatMessage): GroupChatMsgToUser = {
@ -36,7 +36,7 @@ object GroupChatApp {
if (msg.sender.id == SystemUser.ID) {
ChatMessageDAO.insertSystemMsg(meetingId, chat.id, msg.message, messageType, Map(), msg.sender.name)
} else {
ChatMessageDAO.insert(meetingId, chat.id, msg)
ChatMessageDAO.insert(meetingId, chat.id, msg, messageType)
}
val c = chat.add(msg)

View File

@ -9,7 +9,9 @@ class GroupChatHdlrs(implicit val context: ActorContext)
with GetGroupChatMsgsReqMsgHdlr
with GetGroupChatsReqMsgHdlr
with SendGroupChatMessageMsgHdlr
with SyncGetGroupChatsInfoMsgHdlr {
with SendGroupChatMessageFromApiSysPubMsgHdlr
with SetGroupChatVisibleReqMsgHdlr
with SetGroupChatLastSeenReqMsgHdlr {
val log = Logging(context.system, getClass)
}

View File

@ -0,0 +1,46 @@
package org.bigbluebutton.core.apps.groupchats
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.bus.MessageBus
import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.models.SystemUser
import org.bigbluebutton.core.running.{ HandlerHelpers, LiveMeeting }
trait SendGroupChatMessageFromApiSysPubMsgHdlr extends HandlerHelpers {
this: GroupChatHdlrs =>
def handle(msg: SendGroupChatMessageFromApiSysPubMsg, state: MeetingState2x,
liveMeeting: LiveMeeting, bus: MessageBus): MeetingState2x = {
log.debug("RECEIVED SendGroupChatMessageFromApiSysPubMsg {}", msg)
val chatDisabled: Boolean = liveMeeting.props.meetingProp.disabledFeatures.contains("chat")
if (!chatDisabled) {
val newState = for {
sender <- GroupChatApp.findGroupChatUser(SystemUser.ID, liveMeeting.users2x)
chat <- state.groupChats.find(GroupChatApp.MAIN_PUBLIC_CHAT)
} yield {
val groupChatMsgFromUser = GroupChatMsgFromUser(sender.id, sender.copy(name = msg.body.userName), msg.body.message)
val gcm = GroupChatApp.toGroupChatMessage(sender.copy(name = msg.body.userName), groupChatMsgFromUser, emphasizedText = true)
val gcs = GroupChatApp.addGroupChatMessage(liveMeeting.props.meetingProp.intId, chat, state.groupChats, gcm, GroupChatMessageType.API)
val event = buildGroupChatMessageBroadcastEvtMsg(
liveMeeting.props.meetingProp.intId,
msg.body.userName, GroupChatApp.MAIN_PUBLIC_CHAT, gcm
)
bus.outGW.send(event)
state.update(gcs)
}
newState match {
case Some(ns) => ns
case None => state
}
} else {
state
}
}
}

View File

@ -16,6 +16,14 @@ trait SendGroupChatMessageMsgHdlr extends HandlerHelpers {
def handle(msg: SendGroupChatMessageMsg, state: MeetingState2x,
liveMeeting: LiveMeeting, bus: MessageBus): MeetingState2x = {
def determineMessageType(metadata: Map[String, Any]): String = {
if (metadata.contains("pluginName")) {
GroupChatMessageType.PLUGIN
} else {
GroupChatMessageType.DEFAULT
}
}
val chatDisabled: Boolean = liveMeeting.props.meetingProp.disabledFeatures.contains("chat")
var chatLocked: Boolean = false
@ -59,8 +67,10 @@ trait SendGroupChatMessageMsgHdlr extends HandlerHelpers {
!chatIsPrivate &&
sender.role == Roles.MODERATOR_ROLE
val gcm = GroupChatApp.toGroupChatMessage(sender, msg.body.msg, emphasizedText)
val gcs = GroupChatApp.addGroupChatMessage(liveMeeting.props.meetingProp.intId, chat, state.groupChats, gcm)
val messageType = determineMessageType(msg.body.msg.metadata)
val gcm = GroupChatApp.toGroupChatMessage(sender, msg.body.msg, emphasizedText, msg.body.msg.metadata)
val gcs = GroupChatApp.addGroupChatMessage(liveMeeting.props.meetingProp.intId, chat, state.groupChats, gcm, messageType)
val event = buildGroupChatMessageBroadcastEvtMsg(
liveMeeting.props.meetingProp.intId,

View File

@ -0,0 +1,21 @@
package org.bigbluebutton.core.apps.groupchats
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.db.ChatUserDAO
import org.bigbluebutton.core.models.Users2x
import org.bigbluebutton.core.running.LiveMeeting
import java.sql.Timestamp
import java.time.Instant
trait SetGroupChatLastSeenReqMsgHdlr {
def handle(msg: SetGroupChatLastSeenReqMsg, liveMeeting: LiveMeeting): Unit = {
for {
user <- Users2x.findWithIntId(liveMeeting.users2x, msg.header.userId)
} yield {
val lastSeenAtInstant = Instant.parse(msg.body.lastSeenAt)
val lastSeenAtTimestamp = Timestamp.from(lastSeenAtInstant)
ChatUserDAO.updateChatLastSeen(liveMeeting.props.meetingProp.intId, msg.body.chatId, user.intId, lastSeenAtTimestamp)
}
}
}

View File

@ -0,0 +1,17 @@
package org.bigbluebutton.core.apps.groupchats
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.bus.MessageBus
import org.bigbluebutton.core.db.ChatUserDAO
import org.bigbluebutton.core.models.Users2x
import org.bigbluebutton.core.running.{ LiveMeeting, LogHelper }
trait SetGroupChatVisibleReqMsgHdlr {
def handle(msg: SetGroupChatVisibleReqMsg, liveMeeting: LiveMeeting): Unit = {
for {
user <- Users2x.findWithIntId(liveMeeting.users2x, msg.header.userId)
} yield {
ChatUserDAO.updateChatVisible(liveMeeting.props.meetingProp.intId, msg.body.chatId, user.intId, msg.body.visible)
}
}
}

View File

@ -1,53 +0,0 @@
package org.bigbluebutton.core.apps.groupchats
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.bus.MessageBus
import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.running.LiveMeeting
trait SyncGetGroupChatsInfoMsgHdlr {
this: GroupChatHdlrs =>
def handleSyncGetGroupChatsInfo(state: MeetingState2x, liveMeeting: LiveMeeting, bus: MessageBus): MeetingState2x = {
def buildSyncGetGroupChatsRespMsg(allChats: Vector[GroupChatInfo]): BbbCommonEnvCoreMsg = {
val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, liveMeeting.props.meetingProp.intId, "nodeJSapp")
val envelope = BbbCoreEnvelope(SyncGetGroupChatsRespMsg.NAME, routing)
val header = BbbClientMsgHeader(SyncGetGroupChatsRespMsg.NAME, liveMeeting.props.meetingProp.intId, "nodeJSapp")
val body = SyncGetGroupChatsRespMsgBody(allChats)
val event = SyncGetGroupChatsRespMsg(header, body)
BbbCommonEnvCoreMsg(envelope, event)
}
def buildSyncGetGroupChatMsgsRespMsg(msgs: Vector[GroupChatMsgToUser], chatId: String): BbbCommonEnvCoreMsg = {
val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, liveMeeting.props.meetingProp.intId, "nodeJSapp")
val envelope = BbbCoreEnvelope(SyncGetGroupChatMsgsRespMsg.NAME, routing)
val header = BbbClientMsgHeader(SyncGetGroupChatMsgsRespMsg.NAME, liveMeeting.props.meetingProp.intId, "nodeJSapp")
val body = SyncGetGroupChatMsgsRespMsgBody(chatId, msgs)
val event = SyncGetGroupChatMsgsRespMsg(header, body)
BbbCommonEnvCoreMsg(envelope, event)
}
// fetching all the group chats in the meeting
val chats = GroupChatApp.getAllGroupChatsInMeeting(state)
// mapping group chats, while fetching and publishing messages for each group chat
val allChats = chats map (pc => {
val msgs = pc.msgs.toVector map (m => GroupChatMsgToUser(m.id, m.createdOn, m.correlationId,
m.sender, m.chatEmphasizedText, m.message))
val respMsg = buildSyncGetGroupChatMsgsRespMsg(msgs, pc.id)
bus.outGW.send(respMsg)
GroupChatInfo(pc.id, pc.access, pc.createdBy, pc.users)
})
// publishing a message with the group chat info
val respMsg = buildSyncGetGroupChatsRespMsg(allChats)
bus.outGW.send(respMsg)
state
}
}

View File

@ -1,21 +0,0 @@
package org.bigbluebutton.core.apps.meeting
import org.bigbluebutton.common2.domain.DefaultProps
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.running.OutMsgRouter
trait SyncGetMeetingInfoRespMsgHdlr {
val outGW: OutMsgRouter
def handleSyncGetMeetingInfoRespMsg(props: DefaultProps): Unit = {
val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, props.meetingProp.intId, "nodeJSapp")
val envelope = BbbCoreEnvelope(SyncGetMeetingInfoRespMsg.NAME, routing)
val header = BbbCoreBaseHeader(SyncGetMeetingInfoRespMsg.NAME)
val body = SyncGetMeetingInfoRespMsgBody(props)
val event = SyncGetMeetingInfoRespMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
outGW.send(msgEvent)
}
}

View File

@ -1,30 +0,0 @@
package org.bigbluebutton.core.apps.meeting
import org.bigbluebutton.common2.msgs.ValidateConnAuthTokenSysMsg
import org.bigbluebutton.core.models.RegisteredUsers
import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter }
import org.bigbluebutton.core2.message.senders.MsgBuilder
trait ValidateConnAuthTokenSysMsgHdlr {
val liveMeeting: LiveMeeting
val outGW: OutMsgRouter
def handleValidateConnAuthTokenSysMsg(msg: ValidateConnAuthTokenSysMsg): Unit = {
val regUser = RegisteredUsers.getRegisteredUserWithToken(
msg.body.authToken,
msg.body.userId,
liveMeeting.registeredUsers
)
regUser match {
case Some(u) =>
val event = MsgBuilder.buildValidateConnAuthTokenSysRespMsg(msg.body.meetingId, msg.body.userId,
true, msg.body.connId, msg.body.app)
outGW.send(event)
case None =>
val event = MsgBuilder.buildValidateConnAuthTokenSysRespMsg(msg.body.meetingId, msg.body.userId,
false, msg.body.connId, msg.body.app)
outGW.send(event)
}
}
}

View File

@ -1,24 +0,0 @@
package org.bigbluebutton.core.apps.pads
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.bus.MessageBus
import org.bigbluebutton.core.models.Pads
import org.bigbluebutton.core.running.LiveMeeting
trait PadCreateGroupReqMsgHdlr {
this: PadsApp2x =>
def handle(msg: PadCreateGroupReqMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
val padEnabled = msg.body.model match {
case "notes" => !liveMeeting.props.meetingProp.disabledFeatures.contains("sharedNotes")
case "captions" => !liveMeeting.props.meetingProp.disabledFeatures.contains("captions")
case _ => false
}
if (padEnabled && !Pads.hasGroup(liveMeeting.pads, msg.body.externalId)) {
Pads.addGroup(liveMeeting.pads, msg.body.externalId, msg.body.model, msg.body.name, msg.header.userId)
PadslHdlrHelpers.broadcastPadCreateGroupCmdMsg(bus.outGW, liveMeeting.props.meetingProp.intId, msg.body.externalId, msg.body.model)
}
}
}

View File

@ -3,8 +3,7 @@ package org.bigbluebutton.core.apps.pads
import org.apache.pekko.actor.ActorContext
class PadsApp2x(implicit val context: ActorContext)
extends PadCreateGroupReqMsgHdlr
with PadGroupCreatedEvtMsgHdlr
extends PadGroupCreatedEvtMsgHdlr
with PadCreateReqMsgHdlr
with PadCreatedEvtMsgHdlr
with PadCreateSessionReqMsgHdlr

View File

@ -25,21 +25,21 @@ trait PluginDataChannelDeleteEntryMsgHdlr extends HandlerHelpers {
println(s"Data channel '${msg.body.channelName}' not found in plugin '${msg.body.pluginName}'.")
} else {
val hasPermission = for {
deletePermission <- pluginsConfig(msg.body.pluginName).dataChannels(msg.body.channelName).deletePermission
replaceOrDeletePermission <- pluginsConfig(msg.body.pluginName).dataChannels(msg.body.channelName).replaceOrDeletePermission
} yield {
deletePermission.toLowerCase match {
replaceOrDeletePermission.toLowerCase match {
case "all" => true
case "moderator" => user.role == Roles.MODERATOR_ROLE
case "presenter" => user.presenter
case "sender" => {
val senderUserId = PluginDataChannelEntryDAO.getMessageSender(
case "creator" => {
val creatorUserId = PluginDataChannelEntryDAO.getEntryCreator(
meetingId,
msg.body.pluginName,
msg.body.channelName,
msg.body.subChannelName,
msg.body.entryId
)
senderUserId == msg.header.userId
creatorUserId == msg.header.userId
}
case _ => false
}

View File

@ -25,9 +25,9 @@ trait PluginDataChannelPushEntryMsgHdlr extends HandlerHelpers {
println(s"Data channel '${msg.body.channelName}' not found in plugin '${msg.body.pluginName}'.")
} else {
val hasPermission = for {
writePermission <- pluginsConfig(msg.body.pluginName).dataChannels(msg.body.channelName).writePermission
pushPermission <- pluginsConfig(msg.body.pluginName).dataChannels(msg.body.channelName).pushPermission
} yield {
writePermission.toLowerCase match {
pushPermission.toLowerCase match {
case "all" => true
case "moderator" => user.role == Roles.MODERATOR_ROLE
case "presenter" => user.presenter

View File

@ -0,0 +1,63 @@
package org.bigbluebutton.core.apps.plugin
import org.bigbluebutton.ClientSettings
import org.bigbluebutton.common2.msgs.PluginDataChannelReplaceEntryMsg
import org.bigbluebutton.core.db.{JsonUtils, PluginDataChannelEntryDAO}
import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.models.{Roles, Users2x}
import org.bigbluebutton.core.running.{HandlerHelpers, LiveMeeting}
trait PluginDataChannelReplaceEntryMsgHdlr extends HandlerHelpers {
def handle(msg: PluginDataChannelReplaceEntryMsg, 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.channelName)) {
println(s"Data channel '${msg.body.channelName}' not found in plugin '${msg.body.pluginName}'.")
} else {
val hasPermission = for {
replaceOrDeletePermission <- pluginsConfig(msg.body.pluginName).dataChannels(msg.body.channelName).replaceOrDeletePermission
} yield {
replaceOrDeletePermission.toLowerCase match {
case "all" => true
case "moderator" => user.role == Roles.MODERATOR_ROLE
case "presenter" => user.presenter
case "creator" => {
val creatorUserId = PluginDataChannelEntryDAO.getEntryCreator(
meetingId,
msg.body.pluginName,
msg.body.channelName,
msg.body.subChannelName,
msg.body.entryId
)
creatorUserId == msg.header.userId
}
case _ => false
}
}
if (!hasPermission.contains(true)) {
println(s"No permission to write in plugin: '${msg.body.pluginName}', data channel: '${msg.body.channelName}'.")
} else {
PluginDataChannelEntryDAO.replace(
msg.header.meetingId,
msg.body.pluginName,
msg.body.channelName,
msg.body.subChannelName,
msg.body.entryId,
JsonUtils.mapToJson(msg.body.payloadJson),
)
}
}
}
}
}

View File

@ -25,9 +25,9 @@ trait PluginDataChannelResetMsgHdlr extends HandlerHelpers {
println(s"Data channel '${msg.body.channelName}' not found in plugin '${msg.body.pluginName}'.")
} else {
val hasPermission = for {
deletePermission <- pluginsConfig(msg.body.pluginName).dataChannels(msg.body.channelName).deletePermission
replaceOrDeletePermission <- pluginsConfig(msg.body.pluginName).dataChannels(msg.body.channelName).replaceOrDeletePermission
} yield {
deletePermission.toLowerCase match {
replaceOrDeletePermission.toLowerCase match {
case "all" => true
case "moderator" => user.role == Roles.MODERATOR_ROLE
case "presenter" => user.presenter

View File

@ -6,6 +6,7 @@ import org.bigbluebutton.common2.msgs.PluginDataChannelDeleteEntryMsgBody
class PluginHdlrs(implicit val context: ActorContext)
extends PluginDataChannelPushEntryMsgHdlr
with PluginDataChannelReplaceEntryMsgHdlr
with PluginDataChannelDeleteEntryMsgHdlr
with PluginDataChannelResetMsgHdlr {

View File

@ -145,7 +145,7 @@ trait MakePresentationDownloadReqMsgHdlr extends RightsManagementTrait {
&& m.body.fileStateType == "Converted") {
val reason = "Converted presentation download disabled for this meeting. (PDF format)"
PermissionCheck.ejectUserForFailedPermission(meetingId, userId, reason, bus.outGW, liveMeeting)
} else if (permissionFailed(PermissionCheck.MOD_LEVEL, PermissionCheck.VIEWER_LEVEL, liveMeeting.users2x, userId)) {
} else if (permissionFailed(PermissionCheck.GUEST_LEVEL, PermissionCheck.PRESENTER_LEVEL, liveMeeting.users2x, userId)) {
val reason = "No permission to download presentation."
PermissionCheck.ejectUserForFailedPermission(meetingId, userId, reason, bus.outGW, liveMeeting)
} else if (currentPres.isEmpty) {
@ -210,11 +210,17 @@ trait MakePresentationDownloadReqMsgHdlr extends RightsManagementTrait {
// 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, presentationId))
var pres = new PresentationInPod(presentationId, default = false, current = false, name = filename,
pages = Map.empty, downloadable = false, downloadFileExtension = "", removable = true, filenameConverted = filename,
uploadCompleted = false, numPages = 0, errorMsgKey = "", errorDetails = Map.empty)
if (liveMeeting.props.meetingProp.disabledFeatures.contains("importPresentationWithAnnotationsFromBreakoutRooms")) {
log.error(s"Capturing breakout rooms slides disabled in meeting ${meetingId}.")
} else if (currentPres.isEmpty) {
log.error(s"No presentation set in meeting ${meetingId}")
pres = pres.copy(errorMsgKey = "204")
bus.outGW.send(buildBroadcastPresentationConversionUpdateEvtMsg(parentMeetingId, "204", jobId, filename, presentationUploadToken))
PresPresentationDAO.updateConversionStarted(parentMeetingId, pres)
} else {
val allPages: Boolean = m.allPages
val pageCount = currentPres.get.pages.size
@ -239,9 +245,13 @@ trait MakePresentationDownloadReqMsgHdlr extends RightsManagementTrait {
val annotations = new StoredAnnotations(jobId, presId, storeAnnotationPages)
bus.outGW.send(buildStoreAnnotationsInRedisSysMsg(annotations, liveMeeting))
} else {
pres = pres.copy(errorMsgKey = "204")
// Notify that no content is available to capture
bus.outGW.send(buildBroadcastPresentationConversionUpdateEvtMsg(parentMeetingId, "204", jobId, filename, presentationUploadToken))
}
PresPresentationDAO.updateConversionStarted(parentMeetingId, pres)
}
}

View File

@ -13,7 +13,12 @@ trait PresentationConversionUpdatePubMsgHdlr {
def handle(msg: PresentationConversionUpdateSysPubMsg, state: MeetingState2x,
liveMeeting: LiveMeeting, bus: MessageBus): MeetingState2x = {
// broadcastEvent(msg)
val presentationId = msg.body.presentationId
val pres = new PresentationInPod(presentationId, msg.body.presName, default = false, current = false, Map.empty, downloadable = false,
"", removable = true, filenameConverted = msg.body.presName, uploadCompleted = false, numPages = 0, errorDetails = Map.empty)
PresPresentationDAO.updateConversionStarted(liveMeeting.props.meetingProp.intId, pres)
state
}
}

View File

@ -11,6 +11,7 @@ class PresentationPodHdlrs(implicit val context: ActorContext)
with PresentationConversionCompletedSysPubMsgHdlr
with PdfConversionInvalidErrorSysPubMsgHdlr
with SetCurrentPagePubMsgHdlr
with SetPageInfiniteWhiteboardPubMsgHdlr
with SetPresenterInDefaultPodInternalMsgHdlr
with RemovePresentationPubMsgHdlr
with SetPresentationDownloadablePubMsgHdlr
@ -22,7 +23,6 @@ class PresentationPodHdlrs(implicit val context: ActorContext)
with MakePresentationDownloadReqMsgHdlr
with ResizeAndMovePagePubMsgHdlr
with SlideResizedPubMsgHdlr
with SyncGetPresentationPodsMsgHdlr
with RemovePresentationPodPubMsgHdlr
with PresentationPageConvertedSysMsgHdlr
with PresentationPageConversionStartedSysMsgHdlr

View File

@ -0,0 +1,38 @@
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.domain.MeetingState2x
import org.bigbluebutton.core.running.LiveMeeting
import org.bigbluebutton.core.db.PresPageDAO
trait SetPageInfiniteWhiteboardPubMsgHdlr extends RightsManagementTrait {
this: PresentationPodHdlrs =>
def handle(
msg: SetPageInfiniteWhiteboardPubMsg, state: MeetingState2x,
liveMeeting: LiveMeeting, bus: MessageBus
): MeetingState2x = {
if (liveMeeting.props.meetingProp.disabledFeatures.contains("infiniteWhiteboard")) {
val meetingId = liveMeeting.props.meetingProp.intId
val reason = "Infinite whiteboard is disabled for this meeting."
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting)
state
} else if (permissionFailed(PermissionCheck.GUEST_LEVEL, PermissionCheck.PRESENTER_LEVEL, liveMeeting.users2x, msg.header.userId)) {
val meetingId = liveMeeting.props.meetingProp.intId
val reason = "No permission to set infinite whiteboard."
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting)
state
} else {
val pageId = msg.body.pageId
val infiniteWhiteboard = msg.body.infiniteWhiteboard
PresPageDAO.updateInfiniteWhiteboard(pageId, infiniteWhiteboard)
state
}
}
}

View File

@ -1,35 +0,0 @@
package org.bigbluebutton.core.apps.presentationpod
import org.bigbluebutton.common2.domain.PresentationPodVO
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.bus.MessageBus
import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.running.LiveMeeting
trait SyncGetPresentationPodsMsgHdlr {
this: PresentationPodHdlrs =>
def handleSyncGetPresentationPods(state: MeetingState2x, liveMeeting: LiveMeeting, bus: MessageBus): MeetingState2x = {
def buildSyncGetPresentationPodsRespMsg(pods: Vector[PresentationPodVO]): BbbCommonEnvCoreMsg = {
val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, liveMeeting.props.meetingProp.intId, "nodeJSapp")
val envelope = BbbCoreEnvelope(SyncGetPresentationPodsRespMsg.NAME, routing)
val header = BbbClientMsgHeader(SyncGetPresentationPodsRespMsg.NAME, liveMeeting.props.meetingProp.intId, "nodeJSapp")
val body = SyncGetPresentationPodsRespMsgBody(pods)
val event = SyncGetPresentationPodsRespMsg(header, body)
BbbCommonEnvCoreMsg(envelope, event)
}
val pods = PresentationPodsApp.getAllPresentationPodsInMeeting(state)
val podsVO = pods.map(pod => PresentationPodsApp.translatePresentationPodToVO(pod))
val event = buildSyncGetPresentationPodsRespMsg(podsVO)
bus.outGW.send(event)
state
}
}

View File

@ -41,8 +41,7 @@ object ScreenshareApp2x {
class ScreenshareApp2x(implicit val context: ActorContext)
extends GetScreenshareStatusReqMsgHdlr
with ScreenshareRtmpBroadcastStartedVoiceConfEvtMsgHdlr
with ScreenshareRtmpBroadcastStoppedVoiceConfEvtMsgHdlr
with SyncGetScreenshareInfoRespMsgHdlr {
with ScreenshareRtmpBroadcastStoppedVoiceConfEvtMsgHdlr {
val log = Logging(context.system, getClass)

View File

@ -1,39 +0,0 @@
package org.bigbluebutton.core.apps.screenshare
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.apps.ScreenshareModel
import org.bigbluebutton.core.bus.MessageBus
import org.bigbluebutton.core.running.LiveMeeting
trait SyncGetScreenshareInfoRespMsgHdlr {
this: ScreenshareApp2x =>
def handleSyncGetScreenshareInfoRespMsg(liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
val routing = Routing.addMsgToClientRouting(
MessageTypes.BROADCAST_TO_MEETING,
liveMeeting.props.meetingProp.intId,
"nodeJSapp"
)
val envelope = BbbCoreEnvelope(SyncGetScreenshareInfoRespMsg.NAME, routing)
val header = BbbClientMsgHeader(
SyncGetScreenshareInfoRespMsg.NAME,
liveMeeting.props.meetingProp.intId,
"nodeJSapp"
)
val body = SyncGetScreenshareInfoRespMsgBody(
ScreenshareModel.isBroadcastingRTMP(liveMeeting.screenshareModel),
ScreenshareModel.getVoiceConf(liveMeeting.screenshareModel),
ScreenshareModel.getScreenshareConf(liveMeeting.screenshareModel),
ScreenshareModel.getRTMPBroadcastingUrl(liveMeeting.screenshareModel),
ScreenshareModel.getScreenshareVideoWidth(liveMeeting.screenshareModel),
ScreenshareModel.getScreenshareVideoHeight(liveMeeting.screenshareModel),
ScreenshareModel.getTimestamp(liveMeeting.screenshareModel),
ScreenshareModel.getHasAudio(liveMeeting.screenshareModel),
ScreenshareModel.getContentType(liveMeeting.screenshareModel)
)
val event = SyncGetScreenshareInfoRespMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
bus.outGW.send(msgEvent)
}
}

View File

@ -1,17 +0,0 @@
package org.bigbluebutton.core.apps.timer
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.bus.MessageBus
import org.bigbluebutton.core.running.LiveMeeting
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait, TimerModel }
import org.bigbluebutton.core.db.TimerDAO
trait CreateTimerPubMsgHdlr extends RightsManagementTrait {
this: TimerApp2x =>
def handle(msg: CreateTimerPubMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
log.debug("Received CreateTimerPubMsg {}", CreateTimerPubMsg)
TimerModel.createTimer(liveMeeting.timerModel, msg.body.stopwatch, msg.body.time, msg.body.accumulated, msg.body.track)
TimerDAO.update(liveMeeting.props.meetingProp.intId, liveMeeting.timerModel)
}
}

View File

@ -1,6 +1,7 @@
package org.bigbluebutton.core.apps.timer
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.apps.TimerModel.{ isRunning, isStopwatch }
import org.bigbluebutton.core.bus.MessageBus
import org.bigbluebutton.core.running.LiveMeeting
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait, TimerModel }
@ -30,7 +31,6 @@ trait StartTimerReqMsgHdlr extends RightsManagementTrait {
val reason = "You need to be the presenter or moderator to start timer"
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting)
} else {
TimerModel.setStartedAt(liveMeeting.timerModel, System.currentTimeMillis())
TimerModel.setRunning(liveMeeting.timerModel, running = true)
TimerDAO.update(liveMeeting.props.meetingProp.intId, liveMeeting.timerModel)
broadcastEvent()

View File

@ -32,7 +32,7 @@ trait SwitchTimerReqMsgHdlr extends RightsManagementTrait {
val reason = "You need to be the presenter or moderator to switch timer"
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting)
} else {
if (TimerModel.getStopwatch(liveMeeting.timerModel) != msg.body.stopwatch) {
if (TimerModel.isStopwatch(liveMeeting.timerModel) != msg.body.stopwatch) {
TimerModel.setStopwatch(liveMeeting.timerModel, msg.body.stopwatch)
TimerModel.setRunning(liveMeeting.timerModel, running = false)
TimerModel.reset(liveMeeting.timerModel) //Reset on switch Stopwatch/Timer

View File

@ -4,16 +4,14 @@ import org.apache.pekko.actor.ActorContext
import org.apache.pekko.event.Logging
class TimerApp2x(implicit val context: ActorContext)
extends CreateTimerPubMsgHdlr
with ActivateTimerReqMsgHdlr
extends ActivateTimerReqMsgHdlr
with DeactivateTimerReqMsgHdlr
with StartTimerReqMsgHdlr
with StopTimerReqMsgHdlr
with SwitchTimerReqMsgHdlr
with SetTimerReqMsgHdlr
with ResetTimerReqMsgHdlr
with SetTrackReqMsgHdlr
with TimerEndedPubMsgHdlr {
with SetTrackReqMsgHdlr {
val log = Logging(context.system, getClass)
}

View File

@ -1,31 +0,0 @@
package org.bigbluebutton.core.apps.timer
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.bus.MessageBus
import org.bigbluebutton.core.running.LiveMeeting
import org.bigbluebutton.core.apps.{ RightsManagementTrait, TimerModel }
import org.bigbluebutton.core.db.TimerDAO
trait TimerEndedPubMsgHdlr extends RightsManagementTrait {
this: TimerApp2x =>
def handle(msg: TimerEndedPubMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
log.debug("Received timerEndedPubMsg {}", TimerEndedPubMsg)
def broadcastEvent(): Unit = {
val routing = collection.immutable.HashMap("sender" -> "bbb-apps-akka")
val envelope = BbbCoreEnvelope(TimerEndedEvtMsg.NAME, routing)
val header = BbbCoreHeaderWithMeetingId(
TimerEndedEvtMsg.NAME,
liveMeeting.props.meetingProp.intId
)
val body = TimerEndedEvtMsgBody()
val event = TimerEndedEvtMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
bus.outGW.send(msgEvent)
}
TimerModel.setEndedAt(liveMeeting.timerModel, System.currentTimeMillis())
TimerDAO.update(liveMeeting.props.meetingProp.intId, liveMeeting.timerModel)
broadcastEvent()
}
}

View File

@ -39,21 +39,12 @@ trait ChangeUserAwayReqMsgHdlr extends RightsManagementTrait {
user <- Users2x.findWithIntId(liveMeeting.users2x, msg.body.userId)
newUserState <- Users2x.setUserAway(liveMeeting.users2x, user.intId, msg.body.away)
} yield {
if (msg.body.away && user.emoji == "") {
Users2x.setEmojiStatus(liveMeeting.users2x, msg.body.userId, "away")
outGW.send(MsgBuilder.buildUserEmojiChangedEvtMsg(liveMeeting.props.meetingProp.intId, msg.body.userId, "away"))
}
if (msg.body.away == false && user.emoji == "away") {
Users2x.setEmojiStatus(liveMeeting.users2x, msg.body.userId, "none")
outGW.send(MsgBuilder.buildUserEmojiChangedEvtMsg(liveMeeting.props.meetingProp.intId, msg.body.userId, "none"))
}
val msgMeta = Map(
"away" -> msg.body.away
)
if (!(user.role == Roles.VIEWER_ROLE && user.locked && permissions.disablePubChat) && ((user.away && !msg.body.away) || (!user.away && msg.body.away))) {
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)
}

View File

@ -1,61 +0,0 @@
package org.bigbluebutton.core.apps.users
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.models.Users2x
import org.bigbluebutton.core.running.{ BaseMeetingActor, LiveMeeting, OutMsgRouter }
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
import org.bigbluebutton.core2.message.senders.MsgBuilder
trait ChangeUserEmojiCmdMsgHdlr extends RightsManagementTrait {
this: BaseMeetingActor =>
val liveMeeting: LiveMeeting
val outGW: OutMsgRouter
def handleChangeUserEmojiCmdMsg(msg: ChangeUserEmojiCmdMsg) {
val isUserSettingOwnEmoji = (msg.header.userId == msg.body.userId)
val isUserModerator = !permissionFailed(
PermissionCheck.MOD_LEVEL,
PermissionCheck.VIEWER_LEVEL,
liveMeeting.users2x,
msg.header.userId
)
val isUserPresenter = !permissionFailed(
PermissionCheck.VIEWER_LEVEL,
PermissionCheck.PRESENTER_LEVEL,
liveMeeting.users2x,
msg.header.userId
)
val initialEmojiState = Users2x.findWithIntId(liveMeeting.users2x, msg.body.userId).get.emoji
val nextEmojiState = msg.body.emoji
if (isUserSettingOwnEmoji
|| isUserModerator && nextEmojiState.equals("none")
|| isUserPresenter && initialEmojiState.equals("raiseHand") && nextEmojiState.equals("none")) {
for {
uvo <- Users2x.setEmojiStatus(liveMeeting.users2x, msg.body.userId, msg.body.emoji)
} yield {
outGW.send(MsgBuilder.buildUserEmojiChangedEvtMsg(liveMeeting.props.meetingProp.intId, msg.body.userId, msg.body.emoji))
if (initialEmojiState == "raiseHand" || nextEmojiState == "raiseHand") {
Users2x.setUserRaiseHand(liveMeeting.users2x, msg.body.userId, msg.body.emoji == "raiseHand")
outGW.send(MsgBuilder.buildUserRaiseHandChangedEvtMsg(liveMeeting.props.meetingProp.intId, msg.body.userId, msg.body.emoji == "raiseHand"))
}
if (initialEmojiState == "away" || nextEmojiState == "away") {
Users2x.setUserAway(liveMeeting.users2x, msg.body.userId, msg.body.emoji == "away")
outGW.send(MsgBuilder.buildUserAwayChangedEvtMsg(liveMeeting.props.meetingProp.intId, msg.body.userId, msg.body.emoji == "away"))
}
}
} else {
val meetingId = liveMeeting.props.meetingProp.intId
val reason = "No permission to clear change user emoji status."
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, outGW, liveMeeting)
}
}
}

View File

@ -1,42 +0,0 @@
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.running.{ LiveMeeting, OutMsgRouter }
trait ChangeUserMobileFlagReqMsgHdlr extends RightsManagementTrait {
this: UsersApp =>
val liveMeeting: LiveMeeting
val outGW: OutMsgRouter
def handleChangeUserMobileFlagReqMsg(msg: ChangeUserMobileFlagReqMsg): Unit = {
log.info("handleChangeUserMobileFlagReqMsg: mobile={} userId={}", msg.body.mobile, msg.body.userId)
def broadcastUserMobileChanged(user: UserState, mobile: Boolean): Unit = {
val routingChange = Routing.addMsgToClientRouting(
MessageTypes.BROADCAST_TO_MEETING,
liveMeeting.props.meetingProp.intId, user.intId
)
val envelopeChange = BbbCoreEnvelope(UserMobileFlagChangedEvtMsg.NAME, routingChange)
val headerChange = BbbClientMsgHeader(UserMobileFlagChangedEvtMsg.NAME, liveMeeting.props.meetingProp.intId,
user.intId)
val bodyChange = UserMobileFlagChangedEvtMsgBody(user.intId, mobile)
val eventChange = UserMobileFlagChangedEvtMsg(headerChange, bodyChange)
val msgEventChange = BbbCommonEnvCoreMsg(envelopeChange, eventChange)
outGW.send(msgEventChange)
}
for {
user <- Users2x.findWithIntId(liveMeeting.users2x, msg.body.userId)
} yield {
if (user.mobile != msg.body.mobile) {
val userMobile = Users2x.setMobile(liveMeeting.users2x, user)
broadcastUserMobileChanged(userMobile, msg.body.mobile)
}
}
}
}

View File

@ -51,17 +51,6 @@ trait ChangeUserRaiseHandReqMsgHdlr extends RightsManagementTrait {
user <- Users2x.findWithIntId(liveMeeting.users2x, msg.body.userId)
newUserState <- Users2x.setUserRaiseHand(liveMeeting.users2x, user.intId, msg.body.raiseHand)
} yield {
if (msg.body.raiseHand && user.emoji == "") {
Users2x.setEmojiStatus(liveMeeting.users2x, msg.body.userId, "raiseHand")
outGW.send(MsgBuilder.buildUserEmojiChangedEvtMsg(liveMeeting.props.meetingProp.intId, msg.body.userId, "raiseHand"))
}
if (msg.body.raiseHand == false && user.emoji == "raiseHand") {
Users2x.setEmojiStatus(liveMeeting.users2x, msg.body.userId, "none")
outGW.send(MsgBuilder.buildUserEmojiChangedEvtMsg(liveMeeting.props.meetingProp.intId, msg.body.userId, "none"))
}
broadcast(newUserState, msg.body.raiseHand)
}
} else {

View File

@ -1,47 +0,0 @@
package org.bigbluebutton.core.apps.users
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.models.Users2x
import org.bigbluebutton.core.running.{ BaseMeetingActor, LiveMeeting, OutMsgRouter }
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
trait ClearAllUsersEmojiCmdMsgHdlr extends RightsManagementTrait {
this: BaseMeetingActor =>
val liveMeeting: LiveMeeting
val outGW: OutMsgRouter
def handleClearAllUsersEmojiCmdMsg(msg: ClearAllUsersEmojiCmdMsg) {
val isUserModerator = !permissionFailed(
PermissionCheck.MOD_LEVEL,
PermissionCheck.VIEWER_LEVEL,
liveMeeting.users2x,
msg.header.userId
)
if (isUserModerator) {
for {
user <- Users2x.findAll(liveMeeting.users2x)
} yield {
Users2x.setEmojiStatus(liveMeeting.users2x, user.intId, "none")
Users2x.setUserAway(liveMeeting.users2x, user.intId, false)
Users2x.setUserRaiseHand(liveMeeting.users2x, user.intId, false)
}
sendClearedAllUsersEmojiEvtMsg(outGW, liveMeeting.props.meetingProp.intId, msg.header.userId)
} else {
val meetingId = liveMeeting.props.meetingProp.intId
val reason = "No permission to clear users reactions."
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, outGW, liveMeeting)
}
}
def sendClearedAllUsersEmojiEvtMsg(outGW: OutMsgRouter, meetingId: String, userId: String): Unit = {
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, meetingId, userId)
val envelope = BbbCoreEnvelope(ClearedAllUsersEmojiEvtMsg.NAME, routing)
val header = BbbClientMsgHeader(ClearedAllUsersEmojiEvtMsg.NAME, meetingId, userId)
val body = ClearedAllUsersEmojiEvtMsgBody()
val event = ClearedAllUsersEmojiEvtMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
outGW.send(msgEvent)
}
}

View File

@ -70,9 +70,6 @@ trait EjectUserFromMeetingCmdMsgHdlr extends RightsManagementTrait {
log.info("Eject user {} userId={} by {} and ban=" + banUser + " in meeting {}", registeredUser.name, userId, ejectedBy, meetingId)
// send a system message to force disconnection
Sender.sendDisconnectClientSysMsg(meetingId, ru.id, ejectedBy, EjectReasonCode.EJECT_USER, outGW)
// Force reconnection with graphql to refresh permissions
Sender.sendForceUserGraphqlReconnectionSysMsg(liveMeeting.props.meetingProp.intId, registeredUser.id, registeredUser.sessionToken, EjectReasonCode.EJECT_USER, outGW)
}
@ -89,8 +86,6 @@ trait EjectUserFromMeetingCmdMsgHdlr extends RightsManagementTrait {
EjectReasonCode.EJECT_USER,
ban = false
)
// send a system message to force disconnection
Sender.sendDisconnectClientSysMsg(meetingId, userId, ejectedBy, EjectReasonCode.EJECT_USER, outGW)
// Force reconnection with graphql to refresh permissions
Sender.sendForceUserGraphqlReconnectionSysMsg(liveMeeting.props.meetingProp.intId, registeredUser.id, registeredUser.sessionToken, EjectReasonCode.EJECT_USER, outGW)
@ -122,8 +117,6 @@ trait EjectUserFromMeetingSysMsgHdlr {
EjectReasonCode.SYSTEM_EJECT_USER,
ban = false
)
// send a system message to force disconnection
Sender.sendDisconnectClientSysMsg(meetingId, userId, ejectedBy, EjectReasonCode.SYSTEM_EJECT_USER, outGW)
// Force reconnection with graphql to refresh permissions
for {

View File

@ -1,66 +0,0 @@
package org.bigbluebutton.core.apps.users
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.running.{ MeetingActor, OutMsgRouter, LiveMeeting }
import org.bigbluebutton.core2.MeetingStatus2x
import org.bigbluebutton.core2.Permissions
import org.bigbluebutton.core.bus.MessageBus
import org.bigbluebutton.core.domain.MeetingState2x
trait GetLockSettingsReqMsgHdlr {
this: MeetingActor =>
val outGW: OutMsgRouter
def buildLockSettingsResp(meetingId: String, requestedBy: String, settings: Permissions): BbbCommonEnvCoreMsg = {
val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, meetingId, requestedBy)
val envelope = BbbCoreEnvelope(GetLockSettingsRespMsg.NAME, routing)
val body = GetLockSettingsRespMsgBody(
disableCam = settings.disableCam,
disableMic = settings.disableMic,
disablePrivChat = settings.disablePrivChat,
disablePubChat = settings.disablePubChat,
disableNotes = settings.disableNotes,
hideUserList = settings.hideUserList,
lockOnJoin = settings.lockOnJoin,
lockOnJoinConfigurable = settings.lockOnJoinConfigurable,
hideViewersCursor = settings.hideViewersCursor,
hideViewersAnnotation = settings.hideViewersAnnotation
)
val header = BbbClientMsgHeader(GetLockSettingsRespMsg.NAME, meetingId, requestedBy)
val event = GetLockSettingsRespMsg(header, body)
BbbCommonEnvCoreMsg(envelope, event)
}
def buildNotInitializedResp(meetingId: String, requestedBy: String): BbbCommonEnvCoreMsg = {
val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, meetingId, requestedBy)
val envelope = BbbCoreEnvelope(LockSettingsNotInitializedRespMsg.NAME, routing)
val body = LockSettingsNotInitializedRespMsgBody(requestedBy)
val header = BbbClientMsgHeader(LockSettingsNotInitializedRespMsg.NAME, meetingId, requestedBy)
val event = LockSettingsNotInitializedRespMsg(header, body)
BbbCommonEnvCoreMsg(envelope, event)
}
def handleGetLockSettingsReqMsg(msg: GetLockSettingsReqMsg): Unit = {
if (MeetingStatus2x.permisionsInitialized(liveMeeting.status)) {
val settings = MeetingStatus2x.getPermissions(liveMeeting.status)
val event = buildLockSettingsResp(props.meetingProp.intId, msg.body.requesterId, settings)
outGW.send(event)
} else {
val event = buildNotInitializedResp(props.meetingProp.intId, msg.body.requesterId)
outGW.send(event)
}
}
def handleSyncGetLockSettingsMsg(state: MeetingState2x, liveMeeting: LiveMeeting, bus: MessageBus): MeetingState2x = {
if (MeetingStatus2x.permisionsInitialized(liveMeeting.status)) {
val settings = MeetingStatus2x.getPermissions(liveMeeting.status)
val event = buildLockSettingsResp(props.meetingProp.intId, "nodeJSapp", settings)
bus.outGW.send(event)
} else {
val event = buildNotInitializedResp(props.meetingProp.intId, "nodeJSapp")
bus.outGW.send(event)
}
state
}
}

View File

@ -0,0 +1,64 @@
package org.bigbluebutton.core.apps.users
import org.apache.pekko.actor.ActorRef
import org.bigbluebutton.core.api.{ ApiResponseFailure, ApiResponseSuccess, GetUserApiMsg, UserInfosApiMsg }
import org.bigbluebutton.core.models.{ RegisteredUser, RegisteredUsers, Roles, Users2x }
import org.bigbluebutton.core.running.{ HandlerHelpers, LiveMeeting, OutMsgRouter }
import org.bigbluebutton.core2.MeetingStatus2x
trait GetUserApiMsgHdlr extends HandlerHelpers {
this: UsersApp =>
val liveMeeting: LiveMeeting
val outGW: OutMsgRouter
def handleGetUserApiMsg(msg: GetUserApiMsg, actorRef: ActorRef): Unit = {
RegisteredUsers.findWithSessionToken(msg.sessionToken, liveMeeting.registeredUsers) match {
case Some(regUser) =>
log.debug("replying GetUserApiMsg with success")
actorRef ! ApiResponseSuccess("User found!", UserInfosApiMsg(getUserInfoResponse(regUser)))
case None =>
log.debug("User not found, sending failure message")
actorRef ! ApiResponseFailure("User not found", Map())
}
}
private def getUserInfoResponse(regUser: RegisteredUser): Map[String, Any] = {
val isModerator = (regUser.role == Roles.MODERATOR_ROLE)
val isLocked = Users2x.findWithIntId(liveMeeting.users2x, regUser.id).exists(u => u.locked)
val userStateExists = Users2x.findWithIntId(liveMeeting.users2x, regUser.id).nonEmpty
val currentlyInMeeting = regUser.joined && !regUser.loggedOut && !regUser.ejected && userStateExists
val permissions = MeetingStatus2x.getPermissions(liveMeeting.status)
var userInfos: Map[String, Any] = Map()
userInfos += ("returncode" -> "SUCCESS")
userInfos += ("meetingID" -> liveMeeting.props.meetingProp.intId)
userInfos += ("externMeetingID" -> liveMeeting.props.meetingProp.extId)
userInfos += ("externUserID" -> regUser.externId)
userInfos += ("internalUserID" -> regUser.id)
userInfos += ("currentlyInMeeting" -> currentlyInMeeting)
userInfos += ("authToken" -> regUser.authToken)
userInfos += ("sessionToken" -> regUser.sessionToken)
userInfos += ("role" -> regUser.role)
userInfos += ("guest" -> regUser.guest)
userInfos += ("guestStatus" -> regUser.guestStatus)
userInfos += ("moderator" -> isModerator)
userInfos += ("presenter" -> Users2x.userIsInPresenterGroup(liveMeeting.users2x, regUser.id))
if (isModerator || !isLocked) {
userInfos += ("hideViewersCursor" -> false)
userInfos += ("hideViewersAnnotation" -> false)
userInfos += ("hideUserList" -> false)
userInfos += ("webcamsOnlyForModerator" -> false)
} else {
userInfos += ("hideViewersCursor" -> permissions.hideViewersCursor)
userInfos += ("hideViewersAnnotation" -> permissions.hideViewersAnnotation)
userInfos += ("hideUserList" -> permissions.hideUserList)
userInfos += ("webcamsOnlyForModerator" -> MeetingStatus2x.webcamsOnlyForModeratorEnabled(liveMeeting.status))
}
userInfos
}
}

View File

@ -1,16 +0,0 @@
package org.bigbluebutton.core.apps.users
import org.bigbluebutton.common2.msgs.GetUsersMeetingReqMsg
import org.bigbluebutton.core.running.{ HandlerHelpers, LiveMeeting, OutMsgRouter }
trait GetUsersMeetingReqMsgHdlr extends HandlerHelpers {
this: UsersApp =>
val liveMeeting: LiveMeeting
val outGW: OutMsgRouter
def handleGetUsersMeetingReqMsg(msg: GetUsersMeetingReqMsg): Unit = {
sendAllUsersInMeeting(msg.body.userId)
sendAllVoiceUsersInMeeting(msg.body.userId, liveMeeting.voiceUsers, liveMeeting.props.meetingProp.intId)
}
}

View File

@ -2,6 +2,7 @@ package org.bigbluebutton.core.apps.users
import org.bigbluebutton.common2.msgs.MuteUserCmdMsg
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
import org.bigbluebutton.core.apps.voice.VoiceApp
import org.bigbluebutton.core.models.{ Roles, Users2x, VoiceUsers }
import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter }
import org.bigbluebutton.core2.MeetingStatus2x
@ -51,13 +52,12 @@ trait MuteUserCmdMsgHdlr extends RightsManagementTrait {
} else {
if (u.muted != msg.body.mute) {
log.info("Send mute user request. meetingId=" + meetingId + " userId=" + u.intId + " user=" + u)
val event = MsgBuilder.buildMuteUserInVoiceConfSysMsg(
meetingId,
voiceConf,
u.voiceUserId,
VoiceApp.muteUserInVoiceConf(
liveMeeting,
outGW,
u.intId,
msg.body.mute
)
outGW.send(event)
}
}
}

View File

@ -30,6 +30,7 @@ trait RegisterUserReqMsgHdlr {
if (liveMeeting.props.usersProp.maxUserConcurrentAccesses > 0) {
val userConcurrentAccesses = RegisteredUsers.findAllWithExternUserId(regUser.externId, liveMeeting.registeredUsers)
.filter(u => !u.loggedOut)
.filter(u => !u.ejected)
.sortWith((u1, u2) => u1.registeredOn > u2.registeredOn) //Remove older first
val userAvailableSlots = liveMeeting.props.usersProp.maxUserConcurrentAccesses - userConcurrentAccesses.length
@ -46,9 +47,6 @@ trait RegisterUserReqMsgHdlr {
val reason = "user ejected because of duplicate external userid"
UsersApp.ejectUserFromMeeting(outGW, liveMeeting, userToRemove.id, SystemUser.ID, reason, EjectReasonCode.DUPLICATE_USER, ban = false)
// send a system message to force disconnection
Sender.sendDisconnectClientSysMsg(meetingId, userToRemove.id, SystemUser.ID, EjectReasonCode.DUPLICATE_USER, outGW)
// Force reconnection with graphql to refresh permissions
Sender.sendForceUserGraphqlReconnectionSysMsg(liveMeeting.props.meetingProp.intId, userToRemove.id, userToRemove.sessionToken, EjectReasonCode.DUPLICATE_USER, outGW)
}
@ -61,8 +59,8 @@ trait RegisterUserReqMsgHdlr {
val regUser = RegisteredUsers.create(liveMeeting.props.meetingProp.intId, 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.enforceLayout, msg.body.customParameters, false)
msg.body.avatarURL, msg.body.webcamBackgroundURL, ColorPicker.nextColor(liveMeeting.props.meetingProp.intId), msg.body.guest, msg.body.authed,
guestStatus, msg.body.excludeFromDashboard, msg.body.enforceLayout, msg.body.userMetadata, false)
checkUserConcurrentAccesses(regUser)
RegisteredUsers.add(liveMeeting.registeredUsers, regUser, liveMeeting.props.meetingProp.intId)
@ -94,7 +92,7 @@ trait RegisterUserReqMsgHdlr {
val g = GuestApprovedVO(regUser.id, GuestStatus.ALLOW)
UsersApp.approveOrRejectGuest(liveMeeting, outGW, g, SystemUser.ID)
case GuestStatus.WAIT =>
val guest = GuestWaiting(regUser.id, regUser.name, regUser.role, regUser.guest, regUser.avatarURL, regUser.color, regUser.authed, regUser.registeredOn)
val guest = GuestWaiting(regUser.id, regUser.name, regUser.role, regUser.guest, regUser.avatarURL, regUser.webcamBackgroundURL, regUser.color, regUser.authed, regUser.registeredOn)
addGuestToWaitingForApproval(guest, liveMeeting.guestsWaiting)
notifyModeratorsOfGuestWaiting(Vector(guest), liveMeeting.users2x, liveMeeting.props.meetingProp.intId)
val notifyEvent = MsgBuilder.buildNotifyRoleInMeetingEvtMsg(

View File

@ -1,43 +0,0 @@
package org.bigbluebutton.core.apps.users
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.api.SendRecordingTimerInternalMsg
import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter }
import org.bigbluebutton.core.util.TimeUtil
import org.bigbluebutton.core2.MeetingStatus2x
trait SendRecordingTimerInternalMsgHdlr {
this: UsersApp =>
val liveMeeting: LiveMeeting
val outGW: OutMsgRouter
def handleSendRecordingTimerInternalMsg(msg: SendRecordingTimerInternalMsg, state: MeetingState2x): MeetingState2x = {
def buildUpdateRecordingTimerEvtMsg(meetingId: String, recordingTime: Long): BbbCommonEnvCoreMsg = {
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, meetingId, "not-used")
val envelope = BbbCoreEnvelope(UpdateRecordingTimerEvtMsg.NAME, routing)
val body = UpdateRecordingTimerEvtMsgBody(recordingTime)
val header = BbbClientMsgHeader(UpdateRecordingTimerEvtMsg.NAME, meetingId, "not-used")
val event = UpdateRecordingTimerEvtMsg(header, body)
BbbCommonEnvCoreMsg(envelope, event)
}
var newDuration = 0L
if (MeetingStatus2x.isRecording(liveMeeting.status)) {
newDuration = TimeUtil.timeNowInMs()
val tracker = state.recordingTracker.udpateCurrentDuration(newDuration)
val recordingTime = TimeUtil.millisToSeconds(tracker.recordingDuration())
val event = buildUpdateRecordingTimerEvtMsg(liveMeeting.props.meetingProp.intId, recordingTime)
outGW.send(event)
state.update(tracker)
} else {
state
}
}
}

View File

@ -5,8 +5,6 @@ import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter }
import org.bigbluebutton.core2.MeetingStatus2x
import org.bigbluebutton.core.util.TimeUtil
import org.bigbluebutton.core.bus.BigBlueButtonEvent
import org.bigbluebutton.core.api.SendRecordingTimerInternalMsg
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
import org.bigbluebutton.core2.message.senders.MsgBuilder
import org.bigbluebutton.core.apps.voice.VoiceApp
@ -101,7 +99,7 @@ trait SetRecordingStatusCmdMsgHdlr extends RightsManagementTrait {
val tracker = state.recordingTracker.pauseTimer(TimeUtil.timeNowInMs())
newState = state.update(tracker)
}
eventBus.publish(BigBlueButtonEvent(liveMeeting.props.meetingProp.intId, SendRecordingTimerInternalMsg(liveMeeting.props.meetingProp.intId)))
newState
} else {
state

View File

@ -0,0 +1,39 @@
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.running.{ LiveMeeting, OutMsgRouter }
trait SetUserCaptionLocaleMsgHdlr extends RightsManagementTrait {
this: UsersApp =>
val liveMeeting: LiveMeeting
val outGW: OutMsgRouter
def handleSetUserCaptionLocaleReqMsg(msg: SetUserCaptionLocaleReqMsg): Unit = {
log.info("handleSetUserCaptionLocaleReqMsg: locale={} provider={} userId={}", msg.body.locale, msg.body.provider, msg.header.userId)
def broadcastUserCaptionLocaleChanged(user: UserState, locale: String, provider: String): Unit = {
val routingChange = Routing.addMsgToClientRouting(
MessageTypes.BROADCAST_TO_MEETING,
liveMeeting.props.meetingProp.intId, user.intId
)
val envelopeChange = BbbCoreEnvelope(UserCaptionLocaleChangedEvtMsg.NAME, routingChange)
val headerChange = BbbClientMsgHeader(UserCaptionLocaleChangedEvtMsg.NAME, liveMeeting.props.meetingProp.intId, user.intId)
val bodyChange = UserCaptionLocaleChangedEvtMsgBody(locale, provider)
val eventChange = UserCaptionLocaleChangedEvtMsg(headerChange, bodyChange)
val msgEventChange = BbbCommonEnvCoreMsg(envelopeChange, eventChange)
outGW.send(msgEventChange)
}
for {
user <- Users2x.findWithIntId(liveMeeting.users2x, msg.header.userId)
} yield {
Users2x.setUserCaptionLocale(liveMeeting.users2x, msg.header.userId, msg.body.locale)
broadcastUserCaptionLocaleChanged(user, msg.body.locale, msg.body.provider)
}
}
}

View File

@ -0,0 +1,22 @@
package org.bigbluebutton.core.apps.users
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.apps.RightsManagementTrait
import org.bigbluebutton.core.db.{ JsonUtils, UserClientSettingsDAO, UserStateDAO }
import org.bigbluebutton.core.models.Users2x
import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter }
trait SetUserClientSettingsReqMsgHdlr extends RightsManagementTrait {
this: UsersApp =>
val liveMeeting: LiveMeeting
val outGW: OutMsgRouter
def handleSetUserClientSettingsReqMsg(msg: SetUserClientSettingsReqMsg): Unit = {
for {
user <- Users2x.findWithIntId(liveMeeting.users2x, msg.header.userId)
} yield {
UserClientSettingsDAO.insertOrUpdate(liveMeeting.props.meetingProp.intId, user.intId, JsonUtils.mapToJson(msg.body.userClientSettingsJson))
}
}
}

View File

@ -0,0 +1,22 @@
package org.bigbluebutton.core.apps.users
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.apps.RightsManagementTrait
import org.bigbluebutton.core.db.UserStateDAO
import org.bigbluebutton.core.models.Users2x
import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter }
trait SetUserEchoTestRunningReqMsgHdlr extends RightsManagementTrait {
this: UsersApp =>
val liveMeeting: LiveMeeting
val outGW: OutMsgRouter
def handleSetUserEchoTestRunningReqMsg(msg: SetUserEchoTestRunningReqMsg): Unit = {
for {
user <- Users2x.findWithIntId(liveMeeting.users2x, msg.header.userId)
} yield {
UserStateDAO.updateEchoTestRunningAt(liveMeeting.props.meetingProp.intId, user.intId)
}
}
}

View File

@ -4,6 +4,7 @@ import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.models.{ UserState, Users2x }
import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter }
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
import org.bigbluebutton.core.db.{ CaptionLocaleDAO, CaptionTypes }
import org.bigbluebutton.core.domain.MeetingState2x
trait SetUserSpeechLocaleMsgHdlr extends RightsManagementTrait {
@ -34,6 +35,12 @@ trait SetUserSpeechLocaleMsgHdlr extends RightsManagementTrait {
} yield {
var changeLocale: Option[UserState] = None;
changeLocale = Users2x.setUserSpeechLocale(liveMeeting.users2x, msg.header.userId, msg.body.locale)
// Add new CaptionLocale
CaptionLocaleDAO.insertOrUpdateCaptionLocale(
liveMeeting.props.meetingProp.intId,
msg.body.locale, CaptionTypes.AUDIO_TRANSCRIPTION, msg.header.userId
)
broadcastUserSpeechLocaleChanged(user, msg.body.locale, msg.body.provider)
}

View File

@ -32,8 +32,6 @@ trait SetUserSpeechOptionsMsgHdlr extends RightsManagementTrait {
for {
user <- Users2x.findWithIntId(liveMeeting.users2x, msg.header.userId)
} yield {
var changeLocale: Option[UserState] = None;
//changeLocale = Users2x.setUserSpeechLocale(liveMeeting.users2x, msg.header.userId, msg.body.locale)
broadcastUserSpeechOptionsChanged(user, msg.body.partialUtterances, msg.body.minUtteranceLength)
}

View File

@ -1,41 +0,0 @@
package org.bigbluebutton.core.apps.users
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.models.Users2x
import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter }
trait SyncGetUsersMeetingRespMsgHdlr {
this: UsersApp =>
val liveMeeting: LiveMeeting
val outGW: OutMsgRouter
def handleSyncGetUsersMeetingRespMsg(): Unit = {
val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, liveMeeting.props.meetingProp.intId, "nodeJSapp")
val envelope = BbbCoreEnvelope(SyncGetUsersMeetingRespMsg.NAME, routing)
val header = BbbClientMsgHeader(SyncGetUsersMeetingRespMsg.NAME, liveMeeting.props.meetingProp.intId, "nodeJSapp")
val users = Users2x.findAll(liveMeeting.users2x)
val webUsers = users.map { u =>
WebUser(
intId = u.intId,
extId = u.extId,
name = u.name,
role = u.role,
guest = u.guest,
authed = u.authed,
guestStatus = u.guestStatus,
emoji = u.emoji,
locked = u.locked,
presenter = u.presenter,
avatar = u.avatar,
clientType = u.clientType
)
}
val body = SyncGetUsersMeetingRespMsgBody(webUsers)
val event = SyncGetUsersMeetingRespMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
outGW.send(msgEvent)
}
}

View File

@ -1,6 +1,6 @@
package org.bigbluebutton.core.apps.users
import org.bigbluebutton.ClientSettings.{ getConfigPropertyValueByPathAsListOfIntOrElse, getConfigPropertyValueByPathAsListOfStringOrElse }
import org.bigbluebutton.ClientSettings.getConfigPropertyValueByPathAsIntOrElse
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.apps.RightsManagementTrait
import org.bigbluebutton.core.db.UserConnectionStatusDAO
@ -31,25 +31,17 @@ trait UserConnectionAliveReqMsgHdlr extends RightsManagementTrait {
}
def getLevelFromRtt(networkRttInMs: Double): String = {
val levelOptions = getConfigPropertyValueByPathAsListOfStringOrElse(
liveMeeting.clientSettings,
"public.stats.level",
List("warning", "danger", "critical")
)
val clientSettings = liveMeeting.clientSettings
val warningValue = getConfigPropertyValueByPathAsIntOrElse(clientSettings, "public.stats.rtt.warning", 500)
val dangerValue = getConfigPropertyValueByPathAsIntOrElse(clientSettings, "public.stats.rtt.danger", 1000)
val criticalValue = getConfigPropertyValueByPathAsIntOrElse(clientSettings, "public.stats.rtt.critical", 2000)
val rttOptions = getConfigPropertyValueByPathAsListOfIntOrElse(
liveMeeting.clientSettings,
"public.stats.rtt",
List(500, 1000, 2000)
)
val statusRttXLevel = levelOptions.zip(rttOptions).reverse
val statusFound = statusRttXLevel.collectFirst {
case (level, rtt) if networkRttInMs > rtt => level
networkRttInMs match {
case rtt if rtt > criticalValue => "critical"
case rtt if rtt > dangerValue => "danger"
case rtt if rtt > warningValue => "warning"
case _ => "normal"
}
statusFound.getOrElse("normal")
}
}

View File

@ -1,8 +1,9 @@
package org.bigbluebutton.core.apps.users
import org.bigbluebutton.common2.msgs.{ BbbClientMsgHeader, BbbCommonEnvCoreMsg, BbbCoreEnvelope, MessageTypes, Routing, UserMobileFlagChangedEvtMsg, UserMobileFlagChangedEvtMsgBody }
import org.bigbluebutton.core.api.UserEstablishedGraphqlConnectionInternalMsg
import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.models.Users2x
import org.bigbluebutton.core.models.{ RegisteredUsers, UserState, Users2x }
import org.bigbluebutton.core.running.{ HandlerHelpers, LiveMeeting, MeetingActor, OutMsgRouter }
trait UserEstablishedGraphqlConnectionInternalMsgHdlr extends HandlerHelpers {
@ -12,18 +13,57 @@ trait UserEstablishedGraphqlConnectionInternalMsgHdlr extends HandlerHelpers {
val outGW: OutMsgRouter
def handleUserEstablishedGraphqlConnectionInternalMsg(msg: UserEstablishedGraphqlConnectionInternalMsg, state: MeetingState2x): MeetingState2x = {
log.info("Received user established a graphql connection. user {} meetingId={}", msg.userId, liveMeeting.props.meetingProp.intId)
log.info("Received user established a graphql connection. msg={} meetingId={}", msg, liveMeeting.props.meetingProp.intId)
for {
regUser <- RegisteredUsers.findWithUserId(msg.userId, liveMeeting.registeredUsers)
} yield {
RegisteredUsers.updateUserConnectedToGraphql(liveMeeting.registeredUsers, regUser, graphqlConnected = true)
}
Users2x.findWithIntId(liveMeeting.users2x, msg.userId) match {
case Some(reconnectingUser) =>
if (reconnectingUser.userLeftFlag.left) {
case Some(connectingUser) =>
var userNewState = connectingUser
if (connectingUser.userLeftFlag.left) {
log.info("Resetting flag that user left meeting. user {}", msg.userId)
sendUserLeftFlagUpdatedEvtMsg(outGW, liveMeeting, msg.userId, leftFlag = false)
Users2x.resetUserLeftFlag(liveMeeting.users2x, msg.userId)
for {
userUpdated <- Users2x.resetUserLeftFlag(liveMeeting.users2x, msg.userId)
} yield {
userNewState = userUpdated
}
}
//isMobile and clientType are set on join, but if it is a reconnection join is not necessary
//so it need to be set here
if (connectingUser.mobile != msg.isMobile) {
userNewState = Users2x.setMobile(liveMeeting.users2x, userNewState)
broadcastUserMobileChanged(userNewState, msg.isMobile)
}
if (connectingUser.clientType != msg.clientType) {
userNewState = Users2x.setClientType(liveMeeting.users2x, userNewState, msg.clientType)
}
state
case None =>
state
}
}
}
def broadcastUserMobileChanged(user: UserState, mobile: Boolean): Unit = {
val routingChange = Routing.addMsgToClientRouting(
MessageTypes.BROADCAST_TO_MEETING,
liveMeeting.props.meetingProp.intId, user.intId
)
val envelopeChange = BbbCoreEnvelope(UserMobileFlagChangedEvtMsg.NAME, routingChange)
val headerChange = BbbClientMsgHeader(UserMobileFlagChangedEvtMsg.NAME, liveMeeting.props.meetingProp.intId,
user.intId)
val bodyChange = UserMobileFlagChangedEvtMsgBody(user.intId, mobile)
val eventChange = UserMobileFlagChangedEvtMsg(headerChange, bodyChange)
val msgEventChange = BbbCommonEnvCoreMsg(envelopeChange, eventChange)
outGW.send(msgEventChange)
}
}

View File

@ -1,37 +0,0 @@
package org.bigbluebutton.core.apps.users
import org.bigbluebutton.common2.msgs.UserJoinMeetingAfterReconnectReqMsg
import org.bigbluebutton.core.apps.breakout.BreakoutHdlrHelpers
import org.bigbluebutton.core.apps.voice.UserJoinedVoiceConfEvtMsgHdlr
import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.models.Users2x
import org.bigbluebutton.core.running.{ HandlerHelpers, LiveMeeting, MeetingActor, OutMsgRouter }
trait UserJoinMeetingAfterReconnectReqMsgHdlr extends HandlerHelpers with UserJoinedVoiceConfEvtMsgHdlr {
this: MeetingActor =>
val liveMeeting: LiveMeeting
val outGW: OutMsgRouter
def handleUserJoinMeetingAfterReconnectReqMsg(msg: UserJoinMeetingAfterReconnectReqMsg, state: MeetingState2x): MeetingState2x = {
log.info("Received user joined after reconnecting. user {} meetingId={}", msg.body.userId, msg.header.meetingId)
Users2x.findWithIntId(liveMeeting.users2x, msg.body.userId) match {
case Some(reconnectingUser) =>
if (reconnectingUser.userLeftFlag.left) {
log.info("Resetting flag that user left meeting. user {}", msg.body.userId)
// User has reconnected. Just reset it's flag. ralam Oct 23, 2018
sendUserLeftFlagUpdatedEvtMsg(outGW, liveMeeting, msg.body.userId, leftFlag = false)
Users2x.resetUserLeftFlag(liveMeeting.users2x, msg.body.userId)
}
state
case None =>
val newState = userJoinMeeting(outGW, msg.body.authToken, msg.body.clientType, liveMeeting, state)
if (liveMeeting.props.meetingProp.isBreakout) {
BreakoutHdlrHelpers.updateParentMeetingWithUsers(liveMeeting, eventBus)
}
newState
}
}
}

View File

@ -34,6 +34,7 @@ trait UserJoinMeetingReqMsgHdlr extends HandlerHelpers {
val validationResult = for {
_ <- checkIfUserGuestStatusIsAllowed(user)
_ <- checkIfUserIsBanned(user)
_ <- checkIfUserEjected(user)
_ <- checkIfUserLoggedOut(user)
_ <- validateMaxParticipants(user)
} yield user
@ -46,7 +47,7 @@ trait UserJoinMeetingReqMsgHdlr extends HandlerHelpers {
}
private def handleSuccessfulUserJoin(msg: UserJoinMeetingReqMsg, regUser: RegisteredUser) = {
val newState = userJoinMeeting(outGW, msg.body.authToken, msg.body.clientType, liveMeeting, state)
val newState = userJoinMeeting(outGW, msg.body.authToken, msg.body.clientType, msg.body.clientIsMobile, liveMeeting, state)
updateParentMeetingWithNewListOfUsers()
notifyPreviousUsersWithSameExtId(regUser)
clearCachedVoiceUser(regUser)
@ -104,6 +105,14 @@ trait UserJoinMeetingReqMsgHdlr extends HandlerHelpers {
}
}
private def checkIfUserEjected(user: RegisteredUser): Either[(String, String), Unit] = {
if (user.ejected) {
Left(("User had ejected", EjectReasonCode.EJECT_USER))
} else {
Right(())
}
}
private def checkIfUserLoggedOut(user: RegisteredUser): Either[(String, String), Unit] = {
if (user.loggedOut) {
Left(("User had logged out", EjectReasonCode.USER_LOGGED_OUT))

View File

@ -19,6 +19,12 @@ trait UserLeaveReqMsgHdlr extends HandlerHelpers {
def handleUserClosedAllGraphqlConnectionsInternalMsg(msg: UserClosedAllGraphqlConnectionsInternalMsg, state: MeetingState2x): MeetingState2x = {
log.info("Received user closed all graphql connections. user {} meetingId={}", msg.userId, liveMeeting.props.meetingProp.intId)
for {
regUser <- RegisteredUsers.findWithUserId(msg.userId, liveMeeting.registeredUsers)
} yield {
RegisteredUsers.updateUserConnectedToGraphql(liveMeeting.registeredUsers, regUser, graphqlConnected = false)
}
handleUserLeaveReq(msg.userId, liveMeeting.props.meetingProp.intId, loggedOut = false, state)
}

View File

@ -1,19 +0,0 @@
package org.bigbluebutton.core.apps.users
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.models.Users2x
import org.bigbluebutton.core.running.{ BaseMeetingActor, LiveMeeting, OutMsgRouter }
import org.bigbluebutton.core.apps.{ RightsManagementTrait }
trait UserReactionTimeExpiredCmdMsgHdlr extends RightsManagementTrait {
this: BaseMeetingActor =>
val liveMeeting: LiveMeeting
def handleUserReactionTimeExpiredCmdMsg(msg: UserReactionTimeExpiredCmdMsg) {
val isNodeUser = msg.header.userId.equals("nodeJSapp")
if (isNodeUser) {
Users2x.setReactionEmoji(liveMeeting.users2x, msg.body.userId, "none", 0)
}
}
}

View File

@ -4,12 +4,12 @@ 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.api.SetPresenterInDefaultPodInternalMsg
import org.bigbluebutton.core.apps.ExternalVideoModel
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}
import org.bigbluebutton.core2.message.senders.{MsgBuilder, Sender}
import org.bigbluebutton.core.apps.screenshare.ScreenshareApp2x
import org.bigbluebutton.core.db.UserStateDAO
@ -31,7 +31,7 @@ object UsersApp {
u <- RegisteredUsers.findWithUserId(userId, liveMeeting.registeredUsers)
} yield {
RegisteredUsers.eject(u.id, liveMeeting.registeredUsers, false)
RegisteredUsers.eject(u.id, liveMeeting.registeredUsers, ban = false)
val event = MsgBuilder.buildGuestWaitingLeftEvtMsg(liveMeeting.props.meetingProp.intId, u.id)
outGW.send(event)
@ -71,6 +71,13 @@ object UsersApp {
} yield {
sendPresenterAssigned(outGW, meetingId, newPresenter.intId, newPresenter.name, newPresenter.intId)
sendPresenterInPodReq(meetingId, newPresenter.intId)
// Force reconnection with graphql to refresh permissions
for {
regUser <- RegisteredUsers.findWithUserId(newPresenter.intId, liveMeeting.registeredUsers)
} yield {
Sender.sendForceUserGraphqlReconnectionSysMsg(liveMeeting.props.meetingProp.intId, regUser.id, regUser.sessionToken, "role_changed", outGW)
}
}
}
@ -153,21 +160,20 @@ class UsersApp(
val eventBus: InternalEventBus
)(implicit val context: ActorContext)
extends ValidateAuthTokenReqMsgHdlr
with GetUsersMeetingReqMsgHdlr
with RegisterUserReqMsgHdlr
extends RegisterUserReqMsgHdlr
with GetUserApiMsgHdlr
with ChangeUserRoleCmdMsgHdlr
with SetUserSpeechLocaleMsgHdlr
with SetUserCaptionLocaleMsgHdlr
with SetUserClientSettingsReqMsgHdlr
with SetUserEchoTestRunningReqMsgHdlr
with SetUserSpeechOptionsMsgHdlr
with SyncGetUsersMeetingRespMsgHdlr
with LogoutAndEndMeetingCmdMsgHdlr
with SetRecordingStatusCmdMsgHdlr
with RecordAndClearPreviousMarkersCmdMsgHdlr
with SendRecordingTimerInternalMsgHdlr
with GetRecordingStatusReqMsgHdlr
with AssignPresenterReqMsgHdlr
with ChangeUserPinStateReqMsgHdlr
with ChangeUserMobileFlagReqMsgHdlr
with UserConnectionAliveReqMsgHdlr
with ChangeUserReactionEmojiReqMsgHdlr
with ChangeUserRaiseHandReqMsgHdlr

View File

@ -6,11 +6,7 @@ trait UsersApp2x
extends UserLeaveReqMsgHdlr
with LockUserInMeetingCmdMsgHdlr
with LockUsersInMeetingCmdMsgHdlr
with GetLockSettingsReqMsgHdlr
with ChangeUserEmojiCmdMsgHdlr
with ClearAllUsersEmojiCmdMsgHdlr
with ClearAllUsersReactionCmdMsgHdlr
with UserReactionTimeExpiredCmdMsgHdlr {
with ClearAllUsersReactionCmdMsgHdlr {
this: MeetingActor =>

View File

@ -1,117 +0,0 @@
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 }
import org.bigbluebutton.core2.message.senders.MsgBuilder
trait ValidateAuthTokenReqMsgHdlr extends HandlerHelpers {
this: UsersApp =>
val liveMeeting: LiveMeeting
val outGW: OutMsgRouter
val eventBus: InternalEventBus
def handleValidateAuthTokenReqMsg(msg: ValidateAuthTokenReqMsg, state: MeetingState2x): MeetingState2x = {
log.debug(s"Received ValidateAuthTokenReqMsg msg $msg")
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 {
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(user.externId, liveMeeting.registeredUsers) == false) {
Left(("The maximum number of participants allowed for this meeting has been reached.", EjectReasonCode.MAX_PARTICIPANTS))
} else {
Right(())
}
}
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 sendFailedValidateAuthTokenRespMsg(msg: ValidateAuthTokenReqMsg, failReason: String, failReasonCode: String) = {
UserDAO.updateJoinError(msg.header.meetingId, 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 sendSuccessfulValidateAuthTokenRespMsg(user: RegisteredUser) = {
val meetingId = liveMeeting.props.meetingProp.intId
val updatedUser = RegisteredUsers.updateUserLastAuthTokenValidated(liveMeeting.registeredUsers, user)
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 = {
val meetingId = liveMeeting.props.meetingProp.intId
val users = Users2x.findAll(liveMeeting.users2x)
val webUsers = users.map { u =>
WebUser(intId = u.intId, extId = u.extId, name = u.name, role = u.role,
guest = u.guest, authed = u.authed, guestStatus = u.guestStatus, emoji = u.emoji,
locked = u.locked, presenter = u.presenter, avatar = u.avatar, clientType = u.clientType)
}
val event = MsgBuilder.buildGetUsersMeetingRespMsg(meetingId, requesterId, webUsers)
outGW.send(event)
}
def sendAllVoiceUsersInMeeting(requesterId: String, voiceUsers: VoiceUsers, meetingId: String): Unit = {
val vu = VoiceUsers.findAll(voiceUsers).map { u =>
VoiceConfUser(intId = u.intId, voiceUserId = u.voiceUserId, callingWith = u.callingWith, callerName = u.callerName,
callerNum = u.callerNum, color = u.color, muted = u.muted, talking = u.talking, listenOnly = u.listenOnly)
}
val event = MsgBuilder.buildGetVoiceUsersMeetingRespMsg(meetingId, requesterId, vu)
outGW.send(event)
}
}

View File

@ -2,6 +2,7 @@ package org.bigbluebutton.core.apps.voice
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.running.{ LiveMeeting, MeetingActor, OutMsgRouter }
import org.bigbluebutton.core2.MeetingStatus2x
trait GetMicrophonePermissionReqMsgHdlr {
this: MeetingActor =>
@ -16,7 +17,8 @@ trait GetMicrophonePermissionReqMsgHdlr {
voiceConf: String,
userId: String,
sfuSessionId: String,
allowed: Boolean
allowed: Boolean,
muteOnStart: Boolean
): Unit = {
val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, meetingId, userId)
val envelope = BbbCoreEnvelope(GetMicrophonePermissionRespMsg.NAME, routing)
@ -26,7 +28,8 @@ trait GetMicrophonePermissionReqMsgHdlr {
voiceConf,
userId,
sfuSessionId,
allowed
allowed,
muteOnStart
)
val event = GetMicrophonePermissionRespMsg(header, body)
val eventMsg = BbbCommonEnvCoreMsg(envelope, event)
@ -41,13 +44,18 @@ trait GetMicrophonePermissionReqMsgHdlr {
msg.body.voiceConf,
msg.body.callerIdNum
)
// Lock settings should only define whether the user starts muted or not.
// It must not prevent users from joining audio.
val locked = VoiceHdlrHelpers.isMicrophoneSharingLocked(liveMeeting, msg.body.userId)
val muteOnStart = MeetingStatus2x.isMeetingMuted(liveMeeting.status) || locked
broadcastEvent(
liveMeeting.props.meetingProp.intId,
liveMeeting.props.voiceProp.voiceConf,
msg.body.userId,
msg.body.sfuSessionId,
allowed
allowed,
muteOnStart
)
}
}

View File

@ -12,8 +12,20 @@ trait ListenOnlyModeToggledInSfuEvtMsgHdlr {
def handleListenOnlyModeToggledInSfuEvtMsg(msg: ListenOnlyModeToggledInSfuEvtMsg): Unit = {
for {
vu <- VoiceUsers.findWithIntId(liveMeeting.voiceUsers, msg.body.userId)
vu <- VoiceUsers.findWithIntIdAndCallerNum(
liveMeeting.voiceUsers,
msg.body.userId,
msg.body.callerNum
)
} yield {
// Do not execute if the command is asking for the channel to be HELD
// and the channel is already HELD. This is an edge case with the uuid_hold
// command being used through FSESL or fsapi where holding only works via
// the uuid_hold <toggle> subcommand, which may cause the channel to be the
// opposite of what we want.
// The unhold (uuid_hold off) command is not affected by this, but we don't
// want to send it if the channel is already unheld.
if ((msg.body.enabled && !vu.hold) || !msg.body.enabled) {
VoiceApp.holdChannelInVoiceConf(
liveMeeting,
outGW,
@ -21,5 +33,16 @@ trait ListenOnlyModeToggledInSfuEvtMsgHdlr {
msg.body.enabled
)
}
// If the channel is already in the desired state, just make sure
// any pending mute or unmute commands are sent.
VoiceApp.handleChannelHoldChanged(
liveMeeting,
outGW,
msg.body.userId,
vu.uuid,
msg.body.enabled
)
}
}
}

View File

@ -0,0 +1,76 @@
package org.bigbluebutton.core.apps.voice
import org.bigbluebutton.SystemConfiguration
import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter }
import org.bigbluebutton.core2.MeetingStatus2x
import org.bigbluebutton.core2.message.senders.MsgBuilder
class RecordingFileSplitter(
val liveMeeting: LiveMeeting,
val outGW: OutMsgRouter,
val stream: String
) extends SystemConfiguration {
var startRecTimer: java.util.Timer = null
var stopRecTimer: java.util.Timer = null
var currentStreamPath: String = stream
var previousStreamPath: String = stream
val lastPointPos = stream.lastIndexOf('.')
val pathWithoutExtension: String = stream.substring(0, lastPointPos)
val extension: String = stream.substring(lastPointPos, stream.length())
class RecordingFileSplitterStopTask extends java.util.TimerTask {
def run() {
val event = MsgBuilder.buildStopRecordingVoiceConfSysMsg(
liveMeeting.props.meetingProp.intId,
liveMeeting.props.voiceProp.voiceConf,
previousStreamPath
)
outGW.send(event)
}
}
class RecordingFileSplitterStartTask extends java.util.TimerTask {
var currentFileNumber: Int = 0
def run() {
val newStreamPath = pathWithoutExtension + "_" + String.valueOf(currentFileNumber) + extension;
MeetingStatus2x.voiceRecordingStart(liveMeeting.status, newStreamPath)
val event = MsgBuilder.buildStartRecordingVoiceConfSysMsg(
liveMeeting.props.meetingProp.intId,
liveMeeting.props.voiceProp.voiceConf,
newStreamPath
)
outGW.send(event)
if (currentStreamPath == stream) {
// first file, no previous one to stop
previousStreamPath = newStreamPath
} else {
previousStreamPath = currentStreamPath
// stop previous recording, wait 5 seconds to avoid interruptions
stopRecTimer = new java.util.Timer()
stopRecTimer.schedule(new RecordingFileSplitterStopTask(), 5000L)
}
currentStreamPath = newStreamPath
currentFileNumber = currentFileNumber + 1
}
}
def stop() {
startRecTimer.cancel()
if (stopRecTimer != null) {
stopRecTimer.cancel()
}
}
def start(): Unit = {
startRecTimer = new java.util.Timer()
startRecTimer.schedule(
new RecordingFileSplitterStartTask(),
0L, //initial delay
voiceConfRecordFileSplitterIntervalInMinutes * 60000L //subsequent rate
);
}
}

View File

@ -1,34 +0,0 @@
package org.bigbluebutton.core.apps.voice
import org.bigbluebutton.core.running.{ BaseMeetingActor, LiveMeeting }
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.bus.MessageBus
import org.bigbluebutton.core.models.VoiceUsers
import org.bigbluebutton.core.domain.MeetingState2x
trait SyncGetVoiceUsersMsgHdlr {
this: BaseMeetingActor =>
def handleSyncGetVoiceUsersMsg(state: MeetingState2x, liveMeeting: LiveMeeting, bus: MessageBus): MeetingState2x = {
def buildSyncGetVoiceUsersRespMsg(): BbbCommonEnvCoreMsg = {
val voiceUsers = VoiceUsers.findAll(liveMeeting.voiceUsers).map { u =>
VoiceConfUser(intId = u.intId, voiceUserId = u.voiceUserId, callingWith = u.callingWith, callerName = u.callerName,
callerNum = u.callerNum, color = u.color, muted = u.muted, talking = u.talking, listenOnly = u.listenOnly)
}
val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, liveMeeting.props.meetingProp.intId, "nodeJSapp")
val envelope = BbbCoreEnvelope(SyncGetVoiceUsersRespMsg.NAME, routing)
val header = BbbClientMsgHeader(SyncGetVoiceUsersRespMsg.NAME, liveMeeting.props.meetingProp.intId, "nodeJSapp")
val body = SyncGetVoiceUsersRespMsgBody(voiceUsers)
val event = SyncGetVoiceUsersRespMsg(header, body)
BbbCommonEnvCoreMsg(envelope, event)
}
val respMsg = buildSyncGetVoiceUsersRespMsg()
bus.outGW.send(respMsg)
state
}
}

View File

@ -33,7 +33,7 @@ trait UserJoinedVoiceConfEvtMsgHdlr extends SystemConfiguration {
def registerUserInRegisteredUsers() = {
val regUser = RegisteredUsers.create(liveMeeting.props.meetingProp.intId, msg.body.intId, msg.body.voiceUserId,
msg.body.callerIdName, Roles.VIEWER_ROLE, msg.body.intId, "", "", userColor,
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)
}
@ -48,7 +48,6 @@ trait UserJoinedVoiceConfEvtMsgHdlr extends SystemConfiguration {
guest = true,
authed = true,
guestStatus = GuestStatus.WAIT,
emoji = "none",
reactionEmoji = "none",
raiseHand = false,
away = false,
@ -57,6 +56,7 @@ trait UserJoinedVoiceConfEvtMsgHdlr extends SystemConfiguration {
presenter = false,
locked = MeetingStatus2x.getPermissions(liveMeeting.status).lockOnJoin,
avatar = "",
webcamBackground = "",
color = userColor,
clientType = if (isDialInUser) "dial-in-user" else "",
userLeftFlag = UserLeftFlag(false, 0)
@ -66,7 +66,7 @@ trait UserJoinedVoiceConfEvtMsgHdlr extends SystemConfiguration {
def registerUserAsGuest() = {
if (GuestsWaiting.findWithIntId(liveMeeting.guestsWaiting, msg.body.intId) == None) {
val guest = GuestWaiting(msg.body.intId, msg.body.callerIdName, Roles.VIEWER_ROLE, true, "", userColor, true, System.currentTimeMillis())
val guest = GuestWaiting(msg.body.intId, msg.body.callerIdName, Roles.VIEWER_ROLE, true, "", "", userColor, true, System.currentTimeMillis())
GuestsWaiting.add(liveMeeting.guestsWaiting, guest)
notifyModeratorsOfGuestWaiting(guest, liveMeeting.users2x, liveMeeting.props.meetingProp.intId)
@ -112,6 +112,7 @@ trait UserJoinedVoiceConfEvtMsgHdlr extends SystemConfiguration {
if (isDialInUser) {
registerUserInRegisteredUsers()
registerUserInUsers2x()
if (dialInEnforceGuestPolicy) {
guestPolicy match {
case GuestPolicy(policy, setBy) => {
policy match {
@ -121,6 +122,9 @@ trait UserJoinedVoiceConfEvtMsgHdlr extends SystemConfiguration {
}
}
}
} else {
letUserEnter()
}
} else {
// Regular users reach this point after beeing
// allowed to join

View File

@ -49,6 +49,10 @@ trait UserLeftVoiceConfEvtMsgHdlr {
} yield {
VoiceUsers.removeWithIntId(liveMeeting.voiceUsers, liveMeeting.props.meetingProp.intId, user.intId)
broadcastEvent(user)
if (!user.listenOnly) {
VoiceApp.enforceMuteOnStartThreshold(liveMeeting, outGW)
}
}
if (liveMeeting.props.meetingProp.isBreakout) {

View File

@ -22,6 +22,7 @@ import scala.concurrent.duration._
object VoiceApp extends SystemConfiguration {
// Key is userId
var toggleListenOnlyTasks: Map[String, Cancellable] = Map()
var recordingFileSplitters: Map[String, RecordingFileSplitter] = Map()
def genRecordPath(
recordDir: String,
@ -45,6 +46,13 @@ object VoiceApp extends SystemConfiguration {
}
def stopRecordingVoiceConference(liveMeeting: LiveMeeting, outGW: OutMsgRouter): Unit = {
// stop file splitter
val voiceconf = liveMeeting.props.voiceProp.voiceConf
if (recordingFileSplitters.contains(voiceconf)) {
recordingFileSplitters(voiceconf).stop();
recordingFileSplitters = recordingFileSplitters - (voiceconf)
}
val recStreams = MeetingStatus2x.getVoiceRecordingStreams(liveMeeting.status)
recStreams foreach { rs =>
@ -68,6 +76,16 @@ object VoiceApp extends SystemConfiguration {
now,
voiceConfRecordCodec
)
if (voiceConfRecordEnableFileSplitter) {
val voiceconf = liveMeeting.props.voiceProp.voiceConf
if (recordingFileSplitters.contains(voiceconf)) {
recordingFileSplitters(voiceconf).stop();
recordingFileSplitters = recordingFileSplitters - (voiceconf)
}
val fileSplitter = new RecordingFileSplitter(liveMeeting, outGW, recordFile)
recordingFileSplitters = recordingFileSplitters + (voiceconf -> fileSplitter)
fileSplitter.start()
} else {
MeetingStatus2x.voiceRecordingStart(liveMeeting.status, recordFile)
val event = MsgBuilder.buildStartRecordingVoiceConfSysMsg(
liveMeeting.props.meetingProp.intId,
@ -76,6 +94,7 @@ object VoiceApp extends SystemConfiguration {
)
outGW.send(event)
}
}
def broadcastUserMutedVoiceEvtMsg(
meetingId: String,
@ -133,6 +152,7 @@ object VoiceApp extends SystemConfiguration {
liveMeeting,
outGW,
mutedUser.intId,
mutedUser.callerNum,
muted,
toggleListenOnlyAfterMuteTimer
)
@ -150,7 +170,6 @@ object VoiceApp extends SystemConfiguration {
outGW
)
}
}
}
@ -261,7 +280,7 @@ object VoiceApp extends SystemConfiguration {
callingInto: String,
hold: Boolean,
uuid: String = "unused"
): Unit = {
)(implicit context: ActorContext): Unit = {
def broadcastEvent(voiceUserState: VoiceUserState): Unit = {
val routing = Routing.addMsgToClientRouting(
@ -324,10 +343,30 @@ object VoiceApp extends SystemConfiguration {
hold,
uuid
)
val prevTransparentLOStatus = VoiceHdlrHelpers.transparentListenOnlyAllowed(
liveMeeting
)
VoiceUsers.add(liveMeeting.voiceUsers, voiceUserState)
UserVoiceDAO.update(voiceUserState)
UserDAO.updateVoiceUserJoined(voiceUserState)
val newTransparentLOStatus = VoiceHdlrHelpers.transparentListenOnlyAllowed(
liveMeeting
)
if (prevTransparentLOStatus != newTransparentLOStatus) {
// If the transparent listen only mode was activated or deactivated
// we need to update the listen only mode for all users in the meeting
// that are not muted.
handleTransparentLOModeChange(
liveMeeting,
outGW,
newTransparentLOStatus
)
}
broadcastEvent(voiceUserState)
if (liveMeeting.props.meetingProp.isBreakout) {
@ -337,9 +376,11 @@ object VoiceApp extends SystemConfiguration {
)
}
if (!isListenOnly) {
enforceMuteOnStartThreshold(liveMeeting, outGW)
// if the meeting is muted tell freeswitch to mute the new person
if (!isListenOnly
&& MeetingStatus2x.isMeetingMuted(liveMeeting.status)) {
if (MeetingStatus2x.isMeetingMuted(liveMeeting.status)) {
val event = MsgBuilder.buildMuteUserInVoiceConfSysMsg(
liveMeeting.props.meetingProp.intId,
voiceConf,
@ -348,6 +389,7 @@ object VoiceApp extends SystemConfiguration {
)
outGW.send(event)
}
}
// Make sure lock settings are in effect. (ralam dec 6, 2019)
LockSettingsUtil.enforceLockSettingsForVoiceUser(
@ -395,6 +437,10 @@ object VoiceApp extends SystemConfiguration {
} yield {
VoiceUsers.removeWithIntId(liveMeeting.voiceUsers, user.meetingId, user.intId)
broadcastEvent(user)
if (!user.listenOnly) {
enforceMuteOnStartThreshold(liveMeeting, outGW)
}
}
if (liveMeeting.props.meetingProp.isBreakout) {
@ -405,6 +451,43 @@ object VoiceApp extends SystemConfiguration {
}
}
// Once #muteOnStartThreshold number of voice users is hit, we force
// meetingMute on MeetingStatus2x and broadcast MeetingMutedEvtMsg to clients.
// Otherwise, we broadcast MeetingMutedEvtMsg with the original muteOnStart
// muteOnStartThreshold = 0 means no threshold (disabled).
def enforceMuteOnStartThreshold(
liveMeeting: LiveMeeting,
outGW: OutMsgRouter
): Unit = {
val originalMuteOnStart = liveMeeting.props.voiceProp.muteOnStart
if (muteOnStartThreshold == 0) {
return
}
if (VoiceHdlrHelpers.muteOnStartThresholdReached(liveMeeting)) {
if (!MeetingStatus2x.isMeetingMuted(liveMeeting.status)) {
MeetingStatus2x.muteMeeting(liveMeeting.status)
val event = MsgBuilder.buildMeetingMutedEvtMsg(
liveMeeting.props.meetingProp.intId,
SystemUser.ID,
true,
SystemUser.ID
)
outGW.send(event)
}
} else if (MeetingStatus2x.isMeetingMuted(liveMeeting.status) != originalMuteOnStart) {
MeetingStatus2x.setMeetingMuted(liveMeeting.status, originalMuteOnStart)
val event = MsgBuilder.buildMeetingMutedEvtMsg(
liveMeeting.props.meetingProp.intId,
SystemUser.ID,
originalMuteOnStart,
SystemUser.ID
)
outGW.send(event)
}
}
/** Toggle audio for the given user in voice conference.
*
* We first stop the current audio being played, preventing the playback
@ -476,27 +559,62 @@ object VoiceApp extends SystemConfiguration {
}
}
def handleTransparentLOModeChange(
liveMeeting: LiveMeeting,
outGW: OutMsgRouter,
allowed: Boolean,
)(implicit context: ActorContext): Unit = {
VoiceUsers.findAllMutedVoiceUsers(liveMeeting.voiceUsers) foreach { vu =>
if (allowed) {
toggleListenOnlyMode(
liveMeeting,
outGW,
vu.intId,
vu.callerNum,
vu.muted
)
} else {
toggleListenOnlyMode(
liveMeeting,
outGW,
vu.intId,
vu.callerNum,
false
)
}
}
}
def toggleListenOnlyMode(
liveMeeting: LiveMeeting,
outGW: OutMsgRouter,
userId: String,
callerNum: String,
enabled: Boolean,
delay: Int = 0
)(implicit context: ActorContext): Unit = {
implicit def executionContext = context.system.dispatcher
val allowed = VoiceHdlrHelpers.transparentListenOnlyAllowed(liveMeeting)
// Guarantee there are no other tasks for this channel
removeToggleListenOnlyTask(userId)
// If the meeting has not yet hit the minium amount of duplex channels
// for transparent listen only to be enabled, we don't need to do anything
if (!allowed && enabled) {
return
}
def broacastEvent(): Unit = {
val event = MsgBuilder.buildToggleListenOnlyModeSysMsg(
liveMeeting.props.meetingProp.intId,
liveMeeting.props.voiceProp.voiceConf,
userId,
callerNum,
enabled
)
outGW.send(event)
}
// Guarantee there are no other tasks for this channel
removeToggleListenOnlyTask(userId)
if (enabled && delay > 0) {
// If we are enabling listen only mode, we wait a bit before actually
// dispatching the command - the idea is that recently muted users
@ -547,13 +665,15 @@ object VoiceApp extends SystemConfiguration {
hold
) match {
case Some(vu) =>
// Mute vs hold state mismatch, enforce hold state again.
// Mute state is the predominant one here.
if (vu.muted != hold) {
// Mute vs hold state mismatch. Enforce it if the user is unmuted,
// but hold is active, to avoid the user being unable to talk when
// the channel is active again.
if (!vu.muted && vu.hold) {
toggleListenOnlyMode(
liveMeeting,
outGW,
intId,
vu.callerNum,
vu.muted
)
}
@ -570,4 +690,48 @@ object VoiceApp extends SystemConfiguration {
case _ =>
}
}
def muteUserInVoiceConf(
liveMeeting: LiveMeeting,
outGW: OutMsgRouter,
userId: String,
muted: Boolean
)(implicit context: ActorContext): Unit = {
for {
u <- VoiceUsers.findWithIntId(
liveMeeting.voiceUsers,
userId
)
} yield {
if (u.muted != muted) {
val muteEvent = MsgBuilder.buildMuteUserInVoiceConfSysMsg(
liveMeeting.props.meetingProp.intId,
liveMeeting.props.voiceProp.voiceConf,
u.voiceUserId,
muted
)
// If we're unmuting, trigger a channel unhold -> toggle listen only
// mode -> unmute
if (!muted) {
holdChannelInVoiceConf(
liveMeeting,
outGW,
u.uuid,
muted
)
toggleListenOnlyMode(
liveMeeting,
outGW,
u.intId,
u.callerNum,
muted,
0
)
}
outGW.send(muteEvent)
}
}
}
}

View File

@ -17,7 +17,6 @@ trait VoiceApp2x extends UserJoinedVoiceConfEvtMsgHdlr
with UserTalkingInVoiceConfEvtMsgHdlr
with RecordingStartedVoiceConfEvtMsgHdlr
with VoiceConfRunningEvtMsgHdlr
with SyncGetVoiceUsersMsgHdlr
with AudioFloorChangedVoiceConfEvtMsgHdlr
with VoiceConfCallStateEvtMsgHdlr
with UserStatusVoiceConfEvtMsgHdlr

View File

@ -32,10 +32,6 @@ object VoiceHdlrHelpers extends SystemConfiguration {
): Boolean = {
Users2x.findWithIntId(liveMeeting.users2x, userId) match {
case Some(user) => {
val microphoneSharingLocked = LockSettingsUtil.isMicrophoneSharingLocked(
user,
liveMeeting
)
val isCallerBanned = VoiceUsers.isCallerBanned(
callerIdNum,
liveMeeting.voiceUsers
@ -43,11 +39,42 @@ object VoiceHdlrHelpers extends SystemConfiguration {
(applyPermissionCheck &&
!isCallerBanned &&
!microphoneSharingLocked &&
liveMeeting.props.meetingProp.intId == meetingId &&
liveMeeting.props.voiceProp.voiceConf == voiceConf)
}
case _ => false
}
}
def isMicrophoneSharingLocked(
liveMeeting: LiveMeeting,
userId: String
): Boolean = {
Users2x.findWithIntId(liveMeeting.users2x, userId) match {
case Some(user) => LockSettingsUtil.isMicrophoneSharingLocked(
user,
liveMeeting
) && applyPermissionCheck
case _ => false
}
}
def transparentListenOnlyAllowed(liveMeeting: LiveMeeting): Boolean = {
// Transparent listen only meeting-wide activation threshold.
// Threshold is the number of muted duplex audio channels in a meeting.
// 0 means no threshold, all users are subject to it
val mutedDuplexChannels = VoiceUsers.findAllMutedVoiceUsers(liveMeeting.voiceUsers).length
val threshold = transparentListenOnlyThreshold
(threshold == 0) || (mutedDuplexChannels >= threshold)
}
def muteOnStartThresholdReached(liveMeeting: LiveMeeting): Boolean = {
// Mute on start meeting-wide activation threshold.
// Threshold is the number of users in voice.
// muteOnStartThreshold = 0 means no threshold (disabled).
val usersInVoiceConf = VoiceUsers.usersInVoiceConf(liveMeeting.voiceUsers)
muteOnStartThreshold > 0 && usersInVoiceConf >= muteOnStartThreshold
}
}

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