Merge remote-tracking branch 'bbb/v3.0.x-release' into develop
This commit is contained in:
commit
152ca731e1
14
.github/PULL_REQUEST_TEMPLATE.md
vendored
14
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -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
|
||||
|
5
.github/actions/merge-branches/action.yml
vendored
5
.github/actions/merge-branches/action.yml
vendored
@ -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
|
||||
|
36
.github/actions/upload-blob-report/action.yml
vendored
Normal file
36
.github/actions/upload-blob-report/action.yml
vendored
Normal 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
|
@ -5,40 +5,67 @@ 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: |
|
||||
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];
|
||||
let download = await github.rest.actions.downloadArtifact({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
artifact_id: matchArtifact.id,
|
||||
archive_format: 'zip',
|
||||
});
|
||||
let fs = require('fs');
|
||||
fs.writeFileSync(`${process.env.GITHUB_WORKSPACE}/pr-comment-data.zip`, Buffer.from(download.data));
|
||||
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];
|
||||
let download = await github.rest.actions.downloadArtifact({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
artifact_id: matchArtifact.id,
|
||||
archive_format: 'zip',
|
||||
});
|
||||
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: |
|
||||
|
49
.github/workflows/automated-tests.yml
vendored
49
.github/workflows/automated-tests.yml
vendored
@ -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: ./
|
||||
|
11
.github/workflows/check-merge-conflict.yml
vendored
11
.github/workflows/check-merge-conflict.yml
vendored
@ -12,8 +12,11 @@ permissions:
|
||||
jobs:
|
||||
main:
|
||||
permissions:
|
||||
pull-requests: write # for eps1lon/actions-label-merge-conflict to label PRs
|
||||
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
|
||||
@ -21,6 +24,6 @@ jobs:
|
||||
dirtyLabel: "status: conflict"
|
||||
repoToken: "${{ secrets.GITHUB_TOKEN }}"
|
||||
commentOnDirty: |
|
||||
This pull request has conflicts ☹
|
||||
Please resolve those so we can review the pull request.
|
||||
Thanks.
|
||||
This pull request has conflicts ☹
|
||||
Please resolve those so we can review the pull request.
|
||||
Thanks.
|
||||
|
6
.github/workflows/ts-code-compilation.yml
vendored
6
.github/workflows/ts-code-compilation.yml
vendored
@ -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
|
||||
|
6
.github/workflows/ts-code-validation.yml
vendored
6
.github/workflows/ts-code-validation.yml
vendored
@ -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
1
.gitignore
vendored
@ -25,3 +25,4 @@ bbb-presentation-video.zip
|
||||
bbb-presentation-video
|
||||
bbb-graphql-actions-adapter-server/
|
||||
bigbluebutton-html5/public/locales/index.json
|
||||
node_modules
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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])
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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)
|
||||
|
@ -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
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
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
|
||||
}
|
||||
|
@ -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, _]) =>
|
||||
|
@ -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 _ =>
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ trait BreakoutApp2x extends BreakoutRoomCreatedMsgHdlr
|
||||
with SendMessageToAllBreakoutRoomsMsgHdlr
|
||||
with SendMessageToBreakoutRoomInternalMsgHdlr
|
||||
with RequestBreakoutJoinURLReqMsgHdlr
|
||||
with SetBreakoutRoomInviteDismissedReqMsgHdlr
|
||||
with SendBreakoutUsersUpdateMsgHdlr
|
||||
with TransferUserToMeetingRequestHdlr
|
||||
with EndBreakoutRoomInternalMsgHdlr
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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,34 +23,46 @@ trait EndBreakoutRoomInternalMsgHdlr extends HandlerHelpers {
|
||||
}
|
||||
|
||||
if (liveMeeting.props.breakoutProps.captureNotes) {
|
||||
for {
|
||||
group <- Pads.getGroup(liveMeeting.pads, "notes")
|
||||
} yield {
|
||||
val filename = liveMeeting.props.breakoutProps.captureNotesFilename
|
||||
val userId: String = "system"
|
||||
val jobId: String = s"${msg.breakoutId}-notes" // Used as the temporaryPresentationId upon upload
|
||||
val presentationId = PresentationPodsApp.generatePresentationId(filename)
|
||||
|
||||
if (group.rev > 0) {
|
||||
//Request upload of the sharedNotes of breakoutRoom
|
||||
val presentationUploadToken: String = PresentationPodsApp.generateToken("DEFAULT_PRESENTATION_POD", userId)
|
||||
outGW.send(buildPresentationUploadTokenSysPubMsg(msg.parentId, userId, presentationUploadToken, filename, presentationId))
|
||||
|
||||
val exportJob = ExportJob(jobId, "PadCaptureJob", filename, filename, group.padId, "", allPages = true, List(), msg.parentId, presentationUploadToken)
|
||||
val job = buildStoreExportJobInRedisSysMsg(exportJob, liveMeeting)
|
||||
outGW.send(job)
|
||||
} else {
|
||||
// Notify that no content is available
|
||||
val event = buildPresentationConversionUpdateEvtMsg(msg.parentId, presentationId, filename, jobId)
|
||||
outGW.send(event)
|
||||
}
|
||||
}
|
||||
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 {
|
||||
val filename = liveMeeting.props.breakoutProps.captureNotesFilename
|
||||
val userId: String = "system"
|
||||
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)
|
||||
outGW.send(buildPresentationUploadTokenSysPubMsg(msg.parentId, userId, presentationUploadToken, filename, presentationId))
|
||||
|
||||
val exportJob = ExportJob(jobId, "PadCaptureJob", filename, filename, group.padId, "", allPages = true, List(), msg.parentId, presentationUploadToken)
|
||||
val job = buildStoreExportJobInRedisSysMsg(exportJob, liveMeeting)
|
||||
outGW.send(job)
|
||||
} else {
|
||||
pres = pres.copy(errorMsgKey = "204")
|
||||
|
||||
val event = buildPresentationConversionUpdateEvtMsg(msg.parentId, presentationId, filename, jobId)
|
||||
outGW.send(event)
|
||||
}
|
||||
|
||||
PresPresentationDAO.updateConversionStarted(msg.parentId, pres)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
def buildStoreExportJobInRedisSysMsg(exportJob: ExportJob, liveMeeting: LiveMeeting): BbbCommonEnvCoreMsg = {
|
||||
val routing = collection.immutable.HashMap("sender" -> "bbb-apps-akka")
|
||||
val envelope = BbbCoreEnvelope(StoreExportJobInRedisSysMsg.NAME, routing)
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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))
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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,
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -6,6 +6,7 @@ import org.bigbluebutton.common2.msgs.PluginDataChannelDeleteEntryMsgBody
|
||||
|
||||
class PluginHdlrs(implicit val context: ActorContext)
|
||||
extends PluginDataChannelPushEntryMsgHdlr
|
||||
with PluginDataChannelReplaceEntryMsgHdlr
|
||||
with PluginDataChannelDeleteEntryMsgHdlr
|
||||
with PluginDataChannelResetMsgHdlr {
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
}
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -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")
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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))
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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 =>
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -12,11 +12,34 @@ 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 {
|
||||
VoiceApp.holdChannelInVoiceConf(
|
||||
// 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,
|
||||
vu.uuid,
|
||||
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
|
||||
)
|
||||
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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,14 +112,18 @@ trait UserJoinedVoiceConfEvtMsgHdlr extends SystemConfiguration {
|
||||
if (isDialInUser) {
|
||||
registerUserInRegisteredUsers()
|
||||
registerUserInUsers2x()
|
||||
guestPolicy match {
|
||||
case GuestPolicy(policy, setBy) => {
|
||||
policy match {
|
||||
case GuestPolicyType.ALWAYS_ACCEPT => letUserEnter()
|
||||
case GuestPolicyType.ALWAYS_DENY => VoiceApp.removeUserFromVoiceConf(liveMeeting, outGW, msg.body.voiceUserId)
|
||||
case GuestPolicyType.ASK_MODERATOR => registerUserAsGuest()
|
||||
if (dialInEnforceGuestPolicy) {
|
||||
guestPolicy match {
|
||||
case GuestPolicy(policy, setBy) => {
|
||||
policy match {
|
||||
case GuestPolicyType.ALWAYS_ACCEPT => letUserEnter()
|
||||
case GuestPolicyType.ALWAYS_DENY => VoiceApp.removeUserFromVoiceConf(liveMeeting, outGW, msg.body.voiceUserId)
|
||||
case GuestPolicyType.ASK_MODERATOR => registerUserAsGuest()
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
letUserEnter()
|
||||
}
|
||||
} else {
|
||||
// Regular users reach this point after beeing
|
||||
|
@ -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) {
|
||||
|
@ -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,13 +76,24 @@ object VoiceApp extends SystemConfiguration {
|
||||
now,
|
||||
voiceConfRecordCodec
|
||||
)
|
||||
MeetingStatus2x.voiceRecordingStart(liveMeeting.status, recordFile)
|
||||
val event = MsgBuilder.buildStartRecordingVoiceConfSysMsg(
|
||||
liveMeeting.props.meetingProp.intId,
|
||||
liveMeeting.props.voiceProp.voiceConf,
|
||||
recordFile
|
||||
)
|
||||
outGW.send(event)
|
||||
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,
|
||||
liveMeeting.props.voiceProp.voiceConf,
|
||||
recordFile
|
||||
)
|
||||
outGW.send(event)
|
||||
}
|
||||
}
|
||||
|
||||
def broadcastUserMutedVoiceEvtMsg(
|
||||
@ -133,13 +152,14 @@ object VoiceApp extends SystemConfiguration {
|
||||
liveMeeting,
|
||||
outGW,
|
||||
mutedUser.intId,
|
||||
mutedUser.callerNum,
|
||||
muted,
|
||||
toggleListenOnlyAfterMuteTimer
|
||||
)
|
||||
|
||||
// If the user is muted or unmuted with an unheld channel, broadcast
|
||||
// the event right away.
|
||||
// If the user is unmuted, but channel is held, we need to wait for the
|
||||
// If the user is unmuted, but channel is held, we need to wait for the
|
||||
// channel to be active again to broadcast the event. See
|
||||
// VoiceApp.handleChannelHoldChanged for this second case.
|
||||
if (muted || (!muted && !mutedUser.hold)) {
|
||||
@ -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,16 +376,19 @@ object VoiceApp extends SystemConfiguration {
|
||||
)
|
||||
}
|
||||
|
||||
// if the meeting is muted tell freeswitch to mute the new person
|
||||
if (!isListenOnly
|
||||
&& MeetingStatus2x.isMeetingMuted(liveMeeting.status)) {
|
||||
val event = MsgBuilder.buildMuteUserInVoiceConfSysMsg(
|
||||
liveMeeting.props.meetingProp.intId,
|
||||
voiceConf,
|
||||
voiceUserId,
|
||||
true
|
||||
)
|
||||
outGW.send(event)
|
||||
if (!isListenOnly) {
|
||||
enforceMuteOnStartThreshold(liveMeeting, outGW)
|
||||
|
||||
// if the meeting is muted tell freeswitch to mute the new person
|
||||
if (MeetingStatus2x.isMeetingMuted(liveMeeting.status)) {
|
||||
val event = MsgBuilder.buildMuteUserInVoiceConfSysMsg(
|
||||
liveMeeting.props.meetingProp.intId,
|
||||
voiceConf,
|
||||
voiceUserId,
|
||||
true
|
||||
)
|
||||
outGW.send(event)
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure lock settings are in effect. (ralam dec 6, 2019)
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,6 @@ trait VoiceApp2x extends UserJoinedVoiceConfEvtMsgHdlr
|
||||
with UserTalkingInVoiceConfEvtMsgHdlr
|
||||
with RecordingStartedVoiceConfEvtMsgHdlr
|
||||
with VoiceConfRunningEvtMsgHdlr
|
||||
with SyncGetVoiceUsersMsgHdlr
|
||||
with AudioFloorChangedVoiceConfEvtMsgHdlr
|
||||
with VoiceConfCallStateEvtMsgHdlr
|
||||
with UserStatusVoiceConfEvtMsgHdlr
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user