first commit
This commit is contained in:
commit
8ca0e4f293
17
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
17
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: BigBlueButton Setup
|
||||
url: https://groups.google.com/forum/#!forum/bigbluebutton-setup
|
||||
about: Get help with installation, setup and configuration of a BigBlueButton server.
|
||||
- name: BigBlueButton Users
|
||||
url: https://groups.google.com/forum/#!forum/bigbluebutton-users
|
||||
about: Get help and give feedback about using BigBlueButton.
|
||||
- name: BigBlueButton Developers
|
||||
url: https://groups.google.com/forum/#!forum/bigbluebutton-dev
|
||||
about: Ask questions and share experiences with other BigBlueButton developers.
|
||||
- name: Greenlight Frontend
|
||||
url: https://github.com/bigbluebutton/greenlight/issues/
|
||||
about: Issue tracker for the Greenlight frontend
|
||||
- name: Commercial Support
|
||||
url: https://bigbluebutton.org/commercial-support
|
||||
about: List of companies offering commercial BigBlueButton support
|
44
.github/ISSUE_TEMPLATE/core-issue.md
vendored
Normal file
44
.github/ISSUE_TEMPLATE/core-issue.md
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
---
|
||||
name: Server Core Issue
|
||||
about: Template for creating an issue with bbb-web, akka-apps, or other server component
|
||||
title: ''
|
||||
labels: 'module: core'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--PLEASE DO NOT FILE ISSUES FOR GENERAL SUPPORT QUESTIONS.
|
||||
This issue tracker is only for bbb development related issues.-->
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Actual behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. Windows, Mac]
|
||||
- Browser [e.g. Chrome, Safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Smartphone (please complete the following information):**
|
||||
- Device: [e.g. iPhone6]
|
||||
- OS: [e.g. iOS8.1]
|
||||
- Browser [e.g. stock browser, Safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
24
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
24
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: 'type: enhancement'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--PLEASE DO NOT FILE ISSUES FOR GENERAL SUPPORT QUESTIONS.
|
||||
This issue tracker is only for bbb development related issues.
|
||||
Search for existing feature requests to avoid creating duplicates.-->
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
50
.github/ISSUE_TEMPLATE/html5-issue.md
vendored
Normal file
50
.github/ISSUE_TEMPLATE/html5-issue.md
vendored
Normal file
@ -0,0 +1,50 @@
|
||||
---
|
||||
name: HTML5 Issue
|
||||
about: Template for creating HTML5 Issue (frontend which you see during a session, not Greenlight).
|
||||
title: ''
|
||||
labels: 'module: client'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--PLEASE DO NOT FILE ISSUES FOR GENERAL SUPPORT QUESTIONS.
|
||||
This issue tracker is only for bbb development related issues.-->
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Actual behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**BBB version:**
|
||||
BigBlueButton continually evolves. Providing the version/build helps us to pinpoint when an issue was introduced.
|
||||
Example:
|
||||
$ sudo bbb-conf --check | grep BigBlueButton
|
||||
BigBlueButton Server 2.2.2 (1816)
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. Windows, Mac]
|
||||
- Browser [e.g. Chrome, Safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Smartphone (please complete the following information):**
|
||||
- Device: [e.g. iPhone6]
|
||||
- OS: [e.g. iOS8.1]
|
||||
- Browser [e.g. stock browser, Safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
28
.github/ISSUE_TEMPLATE/installation-issue.md
vendored
Normal file
28
.github/ISSUE_TEMPLATE/installation-issue.md
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
---
|
||||
name: Installation issue (not a support question)
|
||||
about: Template for issues encountered during installation
|
||||
title: ''
|
||||
labels: 'deploy: installation'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--PLEASE DO NOT FILE ISSUES FOR GENERAL SUPPORT QUESTIONS.
|
||||
This issue tracker is only for bbb development related issues.
|
||||
For support of BBB installation problems ask in the forum:
|
||||
https://groups.google.com/forum/#!forum/bigbluebutton-setup -->
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**Installation type**
|
||||
Did you install manually or did you use the bbb-install script?
|
||||
|
||||
**Firewall and IP address type**
|
||||
Is your server behind a NAT or firewall? Does your BBB server have its own IPv4 addres?
|
||||
|
||||
**Console output**
|
||||
Please include the full console output from the install.
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
44
.github/ISSUE_TEMPLATE/recording-issue.md
vendored
Normal file
44
.github/ISSUE_TEMPLATE/recording-issue.md
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
---
|
||||
name: Recording Issue
|
||||
about: Template for creating a recording issue
|
||||
title: ''
|
||||
labels: 'module: recording'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--PLEASE DO NOT FILE ISSUES FOR GENERAL SUPPORT QUESTIONS.
|
||||
This issue tracker is only for bbb development related issues.-->
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Actual behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. Windows, Mac]
|
||||
- Browser [e.g. Chrome, Safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Smartphone (please complete the following information):**
|
||||
- Device: [e.g. iPhone6]
|
||||
- OS: [e.g. iOS8.1]
|
||||
- Browser [e.g. stock browser, Safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
26
.github/ISSUE_TEMPLATE/test-scenario.md
vendored
Normal file
26
.github/ISSUE_TEMPLATE/test-scenario.md
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
---
|
||||
name: New Test Case
|
||||
about: Specify a [HTML5 client] test scenario to be added to the automated test coverage suite
|
||||
title: ''
|
||||
labels: 'type: test'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--PLEASE DO NOT FILE ISSUES FOR GENERAL SUPPORT QUESTIONS.
|
||||
This issue tracker is only for bbb development related issues.-->
|
||||
|
||||
**Describe the test scenario, be specific**
|
||||
A clear and concise description of what the test case is.
|
||||
|
||||
Steps to reproduce:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Edge cases**
|
||||
Any specific edge cases to keep in mind?
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
36
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
36
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
<!--
|
||||
PLEASE READ THIS MESSAGE.
|
||||
|
||||
HOW TO WRITE A GOOD PULL REQUEST?
|
||||
|
||||
- Make it small.
|
||||
- Do only one thing.
|
||||
- Avoid re-formatting.
|
||||
- Make sure the code builds and works.
|
||||
- Write useful descriptions and titles.
|
||||
- Address review comments in terms of additional commits.
|
||||
- Do not amend/squash existing ones unless the PR is trivial.
|
||||
- Read the contributing guide: https://docs.bigbluebutton.org/support/faq.html#bigbluebutton-development-process
|
||||
- Sign and send the Contributor License Agreement: https://docs.bigbluebutton.org/support/faq.html#why-do-i-need-to-sign-a-contributor-license-agreement-to-contribute-source-code
|
||||
|
||||
-->
|
||||
|
||||
### 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
|
||||
|
||||
<!-- Anything else we should know when reviewing? -->
|
||||
- [ ] Added/updated documentation
|
19
.github/stale.yml
vendored
Normal file
19
.github/stale.yml
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
# Number of days of inactivity before an issue becomes stale
|
||||
daysUntilStale: 270
|
||||
# Number of days of inactivity before a stale issue is closed
|
||||
daysUntilClose: 90
|
||||
# Issues with these labels will never be considered stale
|
||||
exemptLabels:
|
||||
- "status: accepted"
|
||||
- "status: verify"
|
||||
- "target: security"
|
||||
- "type: discussion"
|
||||
# Label to use when marking an issue as stale
|
||||
staleLabel: "status: stale"
|
||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
This issue has been automatically marked as stale because it has not had
|
||||
recent activity. It will be closed if no further activity occurs. Thank you
|
||||
for your contributions.
|
||||
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||
closeComment: false
|
159
.github/workflows/automated-tests.yml
vendored
Normal file
159
.github/workflows/automated-tests.yml
vendored
Normal file
@ -0,0 +1,159 @@
|
||||
name: 'Automated tests'
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'develop'
|
||||
- 'v2.[5-9].x-release'
|
||||
- 'v[3-9].*.x-release'
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
permissions:
|
||||
contents: read
|
||||
jobs:
|
||||
build-install-and-test:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- run: ./build/get_external_dependencies.sh
|
||||
- run: ./build/setup.sh bbb-apps-akka
|
||||
- run: ./build/setup.sh bbb-config
|
||||
- run: ./build/setup.sh bbb-etherpad
|
||||
- run: ./build/setup.sh bbb-export-annotations
|
||||
- run: ./build/setup.sh bbb-freeswitch-core
|
||||
- run: ./build/setup.sh bbb-freeswitch-sounds
|
||||
- run: ./build/setup.sh bbb-fsesl-akka
|
||||
- run: ./build/setup.sh bbb-html5-nodejs
|
||||
- run: ./build/setup.sh bbb-html5
|
||||
- run: ./build/setup.sh bbb-learning-dashboard
|
||||
- run: ./build/setup.sh bbb-libreoffice-docker
|
||||
- run: ./build/setup.sh bbb-mkclean
|
||||
- run: ./build/setup.sh bbb-pads
|
||||
- run: ./build/setup.sh bbb-playback
|
||||
- run: ./build/setup.sh bbb-playback-notes
|
||||
- run: ./build/setup.sh bbb-playback-podcast
|
||||
- run: ./build/setup.sh bbb-playback-presentation
|
||||
- run: ./build/setup.sh bbb-playback-screenshare
|
||||
- run: ./build/setup.sh bbb-record-core
|
||||
- run: ./build/setup.sh bbb-web
|
||||
- run: ./build/setup.sh bbb-webrtc-sfu
|
||||
- run: ./build/setup.sh bigbluebutton
|
||||
- run: tar cvf artifacts.tar artifacts/
|
||||
- name: Archive packages
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: artifacts.tar
|
||||
path: |
|
||||
artifacts.tar
|
||||
# - name: Fake package build
|
||||
# run: |
|
||||
# sudo sh -c '
|
||||
# echo "Faking a package build (to speed up installation test)"
|
||||
# cd /
|
||||
# wget -q "http://ci.bbbvm.imdt.com.br/artifacts.tar"
|
||||
# tar xf artifacts.tar
|
||||
# '
|
||||
- name: Generate CA
|
||||
run: |
|
||||
sudo sh -c '
|
||||
mkdir /root/bbb-ci-ssl/
|
||||
cd /root/bbb-ci-ssl/
|
||||
openssl rand -base64 48 > /root/bbb-ci-ssl/bbb-dev-ca.pass ;
|
||||
chmod 600 /root/bbb-ci-ssl/bbb-dev-ca.pass ;
|
||||
openssl genrsa -des3 -out bbb-dev-ca.key -passout file:/root/bbb-ci-ssl/bbb-dev-ca.pass 2048 ;
|
||||
openssl req -x509 -new -nodes -key bbb-dev-ca.key -sha256 -days 1460 -passin file:/root/bbb-ci-ssl/bbb-dev-ca.pass -out bbb-dev-ca.crt -subj "/C=CA/ST=BBB/L=BBB/O=BBB/OU=BBB/CN=BBB-DEV" ;
|
||||
'
|
||||
- name: Trust CA
|
||||
run: |
|
||||
sudo sh -c '
|
||||
sudo mkdir /usr/local/share/ca-certificates/bbb-dev/
|
||||
sudo cp /root/bbb-ci-ssl/bbb-dev-ca.crt /usr/local/share/ca-certificates/bbb-dev/
|
||||
sudo chmod 644 /usr/local/share/ca-certificates/bbb-dev/bbb-dev-ca.crt
|
||||
sudo update-ca-certificates
|
||||
'
|
||||
- name: Generate certificate
|
||||
run: |
|
||||
sudo sh -c '
|
||||
cd /root/bbb-ci-ssl/
|
||||
echo "127.0.0.1 bbb-ci.test" >> /etc/hosts
|
||||
openssl genrsa -out bbb-ci.test.key 2048
|
||||
rm bbb-ci.test.csr bbb-ci.test.crt bbb-ci.test.key
|
||||
cat > bbb-ci.test.ext << EOF
|
||||
authorityKeyIdentifier=keyid,issuer
|
||||
basicConstraints=CA:FALSE
|
||||
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
|
||||
subjectAltName = @alt_names
|
||||
[alt_names]
|
||||
DNS.1 = bbb-ci.test
|
||||
EOF
|
||||
openssl req -nodes -newkey rsa:2048 -keyout bbb-ci.test.key -out bbb-ci.test.csr -subj "/C=CA/ST=BBB/L=BBB/O=BBB/OU=BBB/CN=bbb-ci.test" -addext "subjectAltName = DNS:bbb-ci.test"
|
||||
openssl x509 -req -in bbb-ci.test.csr -CA bbb-dev-ca.crt -CAkey bbb-dev-ca.key -CAcreateserial -out bbb-ci.test.crt -days 825 -sha256 -passin file:/root/bbb-ci-ssl/bbb-dev-ca.pass -extfile bbb-ci.test.ext
|
||||
cd
|
||||
|
||||
mkdir -p /local/certs/
|
||||
cp /root/bbb-ci-ssl/bbb-dev-ca.crt /local/certs/
|
||||
cat /root/bbb-ci-ssl/bbb-ci.test.crt > /local/certs/fullchain.pem
|
||||
cat /root/bbb-ci-ssl/bbb-dev-ca.crt >> /local/certs/fullchain.pem
|
||||
cat /root/bbb-ci-ssl/bbb-ci.test.key > /local/certs/privkey.pem
|
||||
'
|
||||
- name: Setup local repository
|
||||
run: |
|
||||
sudo sh -c '
|
||||
apt install -yq dpkg-dev
|
||||
cd /root && wget -q http://ci.bbbvm.imdt.com.br/cache-3rd-part-packages.tar
|
||||
cp -r /home/runner/work/bigbluebutton/bigbluebutton/artifacts/ /artifacts/
|
||||
cd /artifacts && tar xf /root/cache-3rd-part-packages.tar
|
||||
cd /artifacts && dpkg-scanpackages . /dev/null | gzip -9c > Packages.gz
|
||||
echo "deb [trusted=yes] file:/artifacts/ ./" >> /etc/apt/sources.list
|
||||
'
|
||||
- name: Prepare for install
|
||||
run: |
|
||||
sudo sh -c '
|
||||
apt --purge -y remove apache2-bin
|
||||
'
|
||||
- name: Install BBB
|
||||
run: |
|
||||
sudo sh -c '
|
||||
cd /root/ && wget -q https://ubuntu.bigbluebutton.org/bbb-install-2.6.sh -O bbb-install.sh
|
||||
cat bbb-install.sh | sed "s|> /etc/apt/sources.list.d/bigbluebutton.list||g" | bash -s -- -v focal-26-dev -s bbb-ci.test -j -d /certs/
|
||||
bbb-conf --salt bbbci
|
||||
echo "NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/bbb-dev/bbb-dev-ca.crt" >> /usr/share/meteor/bundle/bbb-html5-with-roles.conf
|
||||
bbb-conf --restart
|
||||
'
|
||||
- name: Install test dependencies
|
||||
working-directory: ./bigbluebutton-tests/playwright
|
||||
run: |
|
||||
sh -c '
|
||||
npm install
|
||||
npx playwright install-deps
|
||||
npx playwright install
|
||||
'
|
||||
- name: Run tests
|
||||
working-directory: ./bigbluebutton-tests/playwright
|
||||
env:
|
||||
NODE_EXTRA_CA_CERTS: /usr/local/share/ca-certificates/bbb-dev/bbb-dev-ca.crt
|
||||
ACTIONS_RUNNER_DEBUG: true
|
||||
BBB_URL: https://bbb-ci.test/bigbluebutton/api
|
||||
BBB_SECRET: bbbci
|
||||
run: npm run test-chromium-ci
|
||||
- name: Run Firefox tests
|
||||
working-directory: ./bigbluebutton-tests/playwright
|
||||
if: ${{ contains(github.event.pull_request.labels.*.name, 'test Firefox')
|
||||
|| contains(github.event.pull_request.labels.*.name, 'Test Firefox') }}
|
||||
env:
|
||||
NODE_EXTRA_CA_CERTS: /usr/local/share/ca-certificates/bbb-dev/bbb-dev-ca.crt
|
||||
ACTIONS_RUNNER_DEBUG: true
|
||||
BBB_URL: https://bbb-ci.test/bigbluebutton/api
|
||||
BBB_SECRET: bbbci
|
||||
# patch playwright's firefox so that it uses the system's root certificate authority
|
||||
run: |
|
||||
sh -c '
|
||||
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
|
||||
'
|
||||
- if: always()
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: tests-report
|
||||
path: |
|
||||
bigbluebutton-tests/playwright/playwright-report
|
||||
bigbluebutton-tests/playwright/test-results
|
26
.github/workflows/check-merge-conflict.yml
vendored
Normal file
26
.github/workflows/check-merge-conflict.yml
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
name: Merge conflict check
|
||||
on:
|
||||
push:
|
||||
pull_request_target:
|
||||
types:
|
||||
- opened
|
||||
- synchronize
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
main:
|
||||
permissions:
|
||||
pull-requests: write # for eps1lon/actions-label-merge-conflict to label PRs
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check for dirty pull requests
|
||||
uses: eps1lon/actions-label-merge-conflict@releases/2.x
|
||||
with:
|
||||
dirtyLabel: "status: conflict"
|
||||
repoToken: "${{ secrets.GITHUB_TOKEN }}"
|
||||
commentOnDirty: |
|
||||
This pull request has conflicts ☹
|
||||
Please resolve those so we can review the pull request.
|
||||
Thanks.
|
23
.gitignore
vendored
Normal file
23
.gitignore
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
.DS_Store
|
||||
._.DS_Store*
|
||||
.metadata/
|
||||
record-and-playback/playback-client-js/testmeeting/
|
||||
record-and-playback/playback-web/playback-web-0.1.war
|
||||
record-and-playback/.project
|
||||
**/target/*
|
||||
**/.cache-main
|
||||
**/.cache-tests
|
||||
.vagrant/*
|
||||
**/.settings/*
|
||||
*/.gradle
|
||||
bigbluebutton-web/.settings*
|
||||
bigbluebutton-web/.classpath
|
||||
bigbluebutton-web/.project
|
||||
akka-bbb-apps/akka-bbb-apps*.log
|
||||
bigbluebutton-web/target-eclipse*
|
||||
record-and-playback/.loadpath
|
||||
**/.idea/*
|
||||
*.iml
|
||||
*~
|
||||
cache/*
|
||||
artifacts/*
|
191
.gitlab-ci.yml
Normal file
191
.gitlab-ci.yml
Normal file
@ -0,0 +1,191 @@
|
||||
# set up stages
|
||||
#
|
||||
|
||||
variables:
|
||||
GIT_STRATEGY: fetch
|
||||
|
||||
stages:
|
||||
- change detection
|
||||
- get external dependencies
|
||||
- build
|
||||
- push packages
|
||||
|
||||
# define which docker image to use for builds
|
||||
default:
|
||||
image: gitlab.senfcall.de:5050/senfcall-public/docker-bbb-build:v2022-12-29-grails-524
|
||||
|
||||
# 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
|
||||
# these versions a build exists. If a viable build (from a commit where the
|
||||
# package is identical) is found, that package name and .deb-filename are
|
||||
# written to a file `packages_to_skip.txt` the root of the repo. This file is
|
||||
# passed to the subsequent stages:
|
||||
# - The jobs in the build stage check whether "their" package is listed in
|
||||
# `packages_to_skip.txt` and don't build a new one if it is.
|
||||
# - The bigbluebutton-build job includes the package versions listed in that
|
||||
# file as version-pinned dependencies of the `bigbluebutton` package (instead
|
||||
# of the current commit version)
|
||||
# - The push_packages job sends the filenames of the packages that can be reused
|
||||
# to the server, so they are included with the current branch. (Relevant for
|
||||
# commits that start a new branch and don't change all packages)
|
||||
change_detection:
|
||||
stage: change detection
|
||||
script: build/change_detection.sh
|
||||
artifacts:
|
||||
paths:
|
||||
- packages_to_skip.txt
|
||||
|
||||
# replace placeholder files with actual external repos
|
||||
# (for source and version of the package see the placeholder file)
|
||||
# this step will be obsolete once dependencies can be tracked as
|
||||
# git submodules
|
||||
get_external_dependencies:
|
||||
stage: get external dependencies
|
||||
script: build/get_external_dependencies.sh
|
||||
artifacts:
|
||||
paths:
|
||||
- bbb-etherpad
|
||||
- bbb-webhooks
|
||||
- bbb-webrtc-sfu
|
||||
- freeswitch
|
||||
- bbb-pads
|
||||
- bbb-playback
|
||||
expire_in: 1h 30min
|
||||
|
||||
# template job for build step
|
||||
.build_job:
|
||||
stage: build
|
||||
artifacts:
|
||||
paths:
|
||||
- artifacts/*.deb
|
||||
expire_in: 1h 30min
|
||||
cache:
|
||||
key: $CI_COMMIT_REF_SLUG
|
||||
paths:
|
||||
- cache/.gradle
|
||||
|
||||
# jobs for all packages in the "build" stage (templated from above)
|
||||
bbb-apps-akka-build:
|
||||
extends: .build_job
|
||||
script:
|
||||
- build/setup-inside-docker.sh bbb-apps-akka
|
||||
|
||||
bbb-config-build:
|
||||
extends: .build_job
|
||||
script:
|
||||
- build/setup-inside-docker.sh bbb-config
|
||||
|
||||
bbb-etherpad-build:
|
||||
extends: .build_job
|
||||
script:
|
||||
- build/setup-inside-docker.sh bbb-etherpad
|
||||
|
||||
bbb-freeswitch-core-build:
|
||||
extends: .build_job
|
||||
script:
|
||||
- build/setup-inside-docker.sh bbb-freeswitch-core
|
||||
|
||||
bbb-freeswitch-sounds-build:
|
||||
extends: .build_job
|
||||
script:
|
||||
- build/setup-inside-docker.sh bbb-freeswitch-sounds
|
||||
|
||||
bbb-fsesl-akka-build:
|
||||
extends: .build_job
|
||||
script:
|
||||
- build/setup-inside-docker.sh bbb-fsesl-akka
|
||||
|
||||
bbb-html5-build:
|
||||
extends: .build_job
|
||||
script:
|
||||
- build/setup-inside-docker.sh bbb-html5
|
||||
|
||||
bbb-learning-dashboard-build:
|
||||
extends: .build_job
|
||||
script:
|
||||
- build/setup-inside-docker.sh bbb-learning-dashboard
|
||||
|
||||
bbb-libreoffice-docker-build:
|
||||
extends: .build_job
|
||||
script:
|
||||
- build/setup-inside-docker.sh bbb-libreoffice-docker
|
||||
|
||||
bbb-lti-build:
|
||||
extends: .build_job
|
||||
script:
|
||||
- build/setup-inside-docker.sh bbb-lti
|
||||
|
||||
bbb-mkclean-build:
|
||||
extends: .build_job
|
||||
script:
|
||||
- build/setup-inside-docker.sh bbb-mkclean
|
||||
|
||||
bbb-pads-build:
|
||||
extends: .build_job
|
||||
script:
|
||||
- build/setup-inside-docker.sh bbb-pads
|
||||
|
||||
bbb-playback-build:
|
||||
extends: .build_job
|
||||
script:
|
||||
- build/setup-inside-docker.sh bbb-playback
|
||||
|
||||
bbb-playback-notes-build:
|
||||
extends: .build_job
|
||||
script:
|
||||
- build/setup-inside-docker.sh bbb-playback-notes
|
||||
|
||||
bbb-playback-podcast-build:
|
||||
extends: .build_job
|
||||
script:
|
||||
- build/setup-inside-docker.sh bbb-playback-podcast
|
||||
|
||||
bbb-playback-presentation-build:
|
||||
extends: .build_job
|
||||
script:
|
||||
- build/setup-inside-docker.sh bbb-playback-presentation
|
||||
|
||||
bbb-playback-screenshare-build:
|
||||
extends: .build_job
|
||||
script:
|
||||
- build/setup-inside-docker.sh bbb-playback-screenshare
|
||||
|
||||
bbb-record-core-build:
|
||||
extends: .build_job
|
||||
script:
|
||||
- build/setup-inside-docker.sh bbb-record-core
|
||||
|
||||
bbb-web-build:
|
||||
extends: .build_job
|
||||
script:
|
||||
- build/setup-inside-docker.sh bbb-web
|
||||
|
||||
bbb-webhooks-build:
|
||||
extends: .build_job
|
||||
script:
|
||||
- build/setup-inside-docker.sh bbb-webhooks
|
||||
|
||||
bbb-webrtc-sfu-build:
|
||||
extends: .build_job
|
||||
script:
|
||||
- build/setup-inside-docker.sh bbb-webrtc-sfu
|
||||
|
||||
bigbluebutton-build:
|
||||
extends: .build_job
|
||||
script:
|
||||
- build/setup-inside-docker.sh bigbluebutton
|
||||
|
||||
# upload packages to repo server
|
||||
push_packages:
|
||||
stage: push packages
|
||||
script: build/push_packages.sh
|
||||
resource_group: push_packages
|
||||
|
||||
# uncomment the lines below if you want one final
|
||||
# "artifacts" dir with all packages (increases runtime, fills up space on gitlab server)
|
||||
#artifacts:
|
||||
# paths:
|
||||
# - artifacts/*
|
||||
# expire_in: 2 days
|
||||
|
||||
|
165
LICENSE
Normal file
165
LICENSE
Normal file
@ -0,0 +1,165 @@
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
|
||||
This version of the GNU Lesser General Public License incorporates
|
||||
the terms and conditions of version 3 of the GNU General Public
|
||||
License, supplemented by the additional permissions listed below.
|
||||
|
||||
0. Additional Definitions.
|
||||
|
||||
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||
General Public License.
|
||||
|
||||
"The Library" refers to a covered work governed by this License,
|
||||
other than an Application or a Combined Work as defined below.
|
||||
|
||||
An "Application" is any work that makes use of an interface provided
|
||||
by the Library, but which is not otherwise based on the Library.
|
||||
Defining a subclass of a class defined by the Library is deemed a mode
|
||||
of using an interface provided by the Library.
|
||||
|
||||
A "Combined Work" is a work produced by combining or linking an
|
||||
Application with the Library. The particular version of the Library
|
||||
with which the Combined Work was made is also called the "Linked
|
||||
Version".
|
||||
|
||||
The "Minimal Corresponding Source" for a Combined Work means the
|
||||
Corresponding Source for the Combined Work, excluding any source code
|
||||
for portions of the Combined Work that, considered in isolation, are
|
||||
based on the Application, and not on the Linked Version.
|
||||
|
||||
The "Corresponding Application Code" for a Combined Work means the
|
||||
object code and/or source code for the Application, including any data
|
||||
and utility programs needed for reproducing the Combined Work from the
|
||||
Application, but excluding the System Libraries of the Combined Work.
|
||||
|
||||
1. Exception to Section 3 of the GNU GPL.
|
||||
|
||||
You may convey a covered work under sections 3 and 4 of this License
|
||||
without being bound by section 3 of the GNU GPL.
|
||||
|
||||
2. Conveying Modified Versions.
|
||||
|
||||
If you modify a copy of the Library, and, in your modifications, a
|
||||
facility refers to a function or data to be supplied by an Application
|
||||
that uses the facility (other than as an argument passed when the
|
||||
facility is invoked), then you may convey a copy of the modified
|
||||
version:
|
||||
|
||||
a) under this License, provided that you make a good faith effort to
|
||||
ensure that, in the event an Application does not supply the
|
||||
function or data, the facility still operates, and performs
|
||||
whatever part of its purpose remains meaningful, or
|
||||
|
||||
b) under the GNU GPL, with none of the additional permissions of
|
||||
this License applicable to that copy.
|
||||
|
||||
3. Object Code Incorporating Material from Library Header Files.
|
||||
|
||||
The object code form of an Application may incorporate material from
|
||||
a header file that is part of the Library. You may convey such object
|
||||
code under terms of your choice, provided that, if the incorporated
|
||||
material is not limited to numerical parameters, data structure
|
||||
layouts and accessors, or small macros, inline functions and templates
|
||||
(ten or fewer lines in length), you do both of the following:
|
||||
|
||||
a) Give prominent notice with each copy of the object code that the
|
||||
Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
4. Combined Works.
|
||||
|
||||
You may convey a Combined Work under terms of your choice that,
|
||||
taken together, effectively do not restrict modification of the
|
||||
portions of the Library contained in the Combined Work and reverse
|
||||
engineering for debugging such modifications, if you also do each of
|
||||
the following:
|
||||
|
||||
a) Give prominent notice with each copy of the Combined Work that
|
||||
the Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
c) For a Combined Work that displays copyright notices during
|
||||
execution, include the copyright notice for the Library among
|
||||
these notices, as well as a reference directing the user to the
|
||||
copies of the GNU GPL and this license document.
|
||||
|
||||
d) Do one of the following:
|
||||
|
||||
0) Convey the Minimal Corresponding Source under the terms of this
|
||||
License, and the Corresponding Application Code in a form
|
||||
suitable for, and under terms that permit, the user to
|
||||
recombine or relink the Application with a modified version of
|
||||
the Linked Version to produce a modified Combined Work, in the
|
||||
manner specified by section 6 of the GNU GPL for conveying
|
||||
Corresponding Source.
|
||||
|
||||
1) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (a) uses at run time
|
||||
a copy of the Library already present on the user's computer
|
||||
system, and (b) will operate properly with a modified version
|
||||
of the Library that is interface-compatible with the Linked
|
||||
Version.
|
||||
|
||||
e) Provide Installation Information, but only if you would otherwise
|
||||
be required to provide such information under section 6 of the
|
||||
GNU GPL, and only to the extent that such information is
|
||||
necessary to install and execute a modified version of the
|
||||
Combined Work produced by recombining or relinking the
|
||||
Application with a modified version of the Linked Version. (If
|
||||
you use option 4d0, the Installation Information must accompany
|
||||
the Minimal Corresponding Source and Corresponding Application
|
||||
Code. If you use option 4d1, you must provide the Installation
|
||||
Information in the manner specified by section 6 of the GNU GPL
|
||||
for conveying Corresponding Source.)
|
||||
|
||||
5. Combined Libraries.
|
||||
|
||||
You may place library facilities that are a work based on the
|
||||
Library side by side in a single library together with other library
|
||||
facilities that are not Applications and are not covered by this
|
||||
License, and convey such a combined library under terms of your
|
||||
choice, if you do both of the following:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work based
|
||||
on the Library, uncombined with any other library facilities,
|
||||
conveyed under the terms of this License.
|
||||
|
||||
b) Give prominent notice with the combined library that part of it
|
||||
is a work based on the Library, and explaining where to find the
|
||||
accompanying uncombined form of the same work.
|
||||
|
||||
6. Revised Versions of the GNU Lesser General Public License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions
|
||||
of the GNU Lesser General Public License from time to time. Such new
|
||||
versions will be similar in spirit to the present version, but may
|
||||
differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Library as you received it specifies that a certain numbered version
|
||||
of the GNU Lesser General Public License "or any later version"
|
||||
applies to it, you have the option of following the terms and
|
||||
conditions either of that published version or of any later version
|
||||
published by the Free Software Foundation. If the Library as you
|
||||
received it does not specify a version number of the GNU Lesser
|
||||
General Public License, you may choose any version of the GNU Lesser
|
||||
General Public License ever published by the Free Software Foundation.
|
||||
|
||||
If the Library as you received it specifies that a proxy can decide
|
||||
whether future versions of the GNU Lesser General Public License shall
|
||||
apply, that proxy's public statement of acceptance of any version is
|
||||
permanent authorization for you to choose that version for the
|
||||
Library.
|
20
README.md
Normal file
20
README.md
Normal file
@ -0,0 +1,20 @@
|
||||
BigBlueButton
|
||||
=============
|
||||
BigBlueButton is an open source web conferencing system.
|
||||
|
||||
BigBlueButton supports real-time sharing of audio, video, slides (with whiteboard controls), chat, and the screen. Instructors can engage remote students with polling, emojis, multi-user whiteboard, and breakout rooms.
|
||||
|
||||
Presenters can record and playback content for later sharing with others.
|
||||
|
||||
We designed BigBlueButton for online learning, (though it can be used for many [other applications](https://www.c4isrnet.com/it-networks/2015/02/11/disa-to-replace-dco-with-new-collaboration-services-tool/) as well). The educational use cases for BigBlueButton are
|
||||
|
||||
* Online tutoring (one-to-one)
|
||||
* Flipped classrooms (recording content ahead of your session)
|
||||
* Group collaboration (many-to-many)
|
||||
* Online classes (one-to-many)
|
||||
|
||||
You can install on a Ubuntu 20.04 64-bit server. We provide [bbb-install.sh](https://github.com/bigbluebutton/bbb-install) to let you have a server up and running within 30 minutes (or your money back 😉).
|
||||
|
||||
For full technical documentation BigBlueButton -- including architecture, features, API, and GreenLight (the default front-end) -- see [https://docs.bigbluebutton.org/](https://docs.bigbluebutton.org/).
|
||||
|
||||
BigBlueButton and the BigBlueButton Logo are trademarks of [BigBlueButton Inc](https://bigbluebutton.org) .
|
22
SECURITY.md
Normal file
22
SECURITY.md
Normal file
@ -0,0 +1,22 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
We actively support BigBlueButton through the community forums and through security updates.
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 2.3.x (or earlier) | :x: |
|
||||
| 2.4.x | :white_check_mark: |
|
||||
| 2.5.x | :white_check_mark: |
|
||||
| 2.6.x | :x: |
|
||||
|
||||
We have released 2.5 to the community and are going to support both 2.4 and 2.5 together for the coming months (while we're actively developing the next release). Also, BigBlueButton 2.3 is now end of life.
|
||||
|
||||
As such, we recommend that all administrators deploy 2.5 going forward. You'll find [many improvements](https://docs.bigbluebutton.org/2.5/new.html) in this newer version.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
If you believe you have found a security vunerability in BigBlueButton please let us know directly by e-mailing security@bigbluebutton.org with as much detail as possible.
|
||||
|
||||
Regards,... [BigBlueButton Team](https://docs.bigbluebutton.org/support/faq.html#bigbluebutton-committer)
|
52
akka-bbb-apps/.gitignore
vendored
Normal file
52
akka-bbb-apps/.gitignore
vendored
Normal file
@ -0,0 +1,52 @@
|
||||
.metadata
|
||||
.project
|
||||
.classpath
|
||||
.settings
|
||||
.history
|
||||
.worksheet
|
||||
gen
|
||||
**/*.swp
|
||||
**/*~.nib
|
||||
**/build/
|
||||
**/*.pbxuser
|
||||
**/*.perspective
|
||||
**/*.perspectivev3
|
||||
*.xcworkspace
|
||||
*.xcuserdatad
|
||||
*.iml
|
||||
project/*.ipr
|
||||
project/*.iml
|
||||
project/*.iws
|
||||
project/out
|
||||
project/*/target
|
||||
project/target
|
||||
project/*/bin
|
||||
project/*/build
|
||||
project/*.iml
|
||||
project/*/*.iml
|
||||
project/.idea
|
||||
project/.idea/*
|
||||
.idea/
|
||||
.DS_Store
|
||||
project/.DS_Store
|
||||
project/*/.DS_Store
|
||||
tm.out
|
||||
tmlog*.log
|
||||
*.tm*.epoch
|
||||
out/
|
||||
provisioning/.vagrant
|
||||
provisioning/*/.vagrant
|
||||
provisioning/*/*.known
|
||||
/sbt/akka-patterns-store/
|
||||
/daemon/src/build/
|
||||
*.lock
|
||||
logs/
|
||||
tmp/
|
||||
build/
|
||||
akka-patterns-store/
|
||||
lib_managed/
|
||||
.cache
|
||||
bin/
|
||||
src/main/resources/
|
||||
.bsp/
|
||||
|
31
akka-bbb-apps/.scalariform.conf
Normal file
31
akka-bbb-apps/.scalariform.conf
Normal file
@ -0,0 +1,31 @@
|
||||
#alignArguments=false
|
||||
alignParameters=true
|
||||
alignSingleLineCaseStatements=true
|
||||
#alignSingleLineCaseStatements.maxArrowIndent=40
|
||||
#allowParamGroupsOnNewlines=false
|
||||
#compactControlReadability=false
|
||||
#compactStringConcatenation=false
|
||||
danglingCloseParenthesis=Force
|
||||
#doubleIndentClassDeclaration=false
|
||||
doubleIndentConstructorArguments=true
|
||||
doubleIndentMethodDeclaration=true
|
||||
firstArgumentOnNewline=Force
|
||||
firstParameterOnNewline=Force
|
||||
#formatXml=true
|
||||
#indentLocalDefs=false
|
||||
#indentPackageBlocks=true
|
||||
#indentSpaces=2
|
||||
#indentWithTabs=false
|
||||
#multilineScaladocCommentsStartOnFirstLine=false
|
||||
#newlineAtEndOfFile=false
|
||||
#placeScaladocAsterisksBeneathSecondAsterisk=false
|
||||
#preserveSpaceBeforeArguments=false
|
||||
#rewriteArrowSymbols=false
|
||||
singleCasePatternOnNewline=false
|
||||
#spaceBeforeColon=false
|
||||
#spaceBeforeContextColon=false
|
||||
#spaceInsideBrackets=false
|
||||
#spaceInsideParentheses=false
|
||||
#spacesAroundMultiImports=true
|
||||
#spacesWithinPatternBinders=true
|
||||
|
79
akka-bbb-apps/build.sbt
Executable file
79
akka-bbb-apps/build.sbt
Executable file
@ -0,0 +1,79 @@
|
||||
import org.bigbluebutton.build._
|
||||
|
||||
import NativePackagerHelper._
|
||||
import com.typesafe.sbt.SbtNativePackager.autoImport._
|
||||
|
||||
enablePlugins(JavaServerAppPackaging)
|
||||
enablePlugins(UniversalPlugin)
|
||||
enablePlugins(DebianPlugin)
|
||||
|
||||
version := "0.0.4"
|
||||
|
||||
val compileSettings = Seq(
|
||||
organization := "org.bigbluebutton",
|
||||
|
||||
scalacOptions ++= List(
|
||||
"-unchecked",
|
||||
"-deprecation",
|
||||
"-Xlint",
|
||||
"-Ywarn-dead-code",
|
||||
"-language:_",
|
||||
"-target:11",
|
||||
"-encoding", "UTF-8"
|
||||
),
|
||||
javacOptions ++= List(
|
||||
"-Xlint:unchecked",
|
||||
"-Xlint:deprecation"
|
||||
)
|
||||
)
|
||||
|
||||
publishTo := Some(Resolver.file("file", new File(Path.userHome.absolutePath + "/dev/repo/maven-repo/releases")))
|
||||
|
||||
// We want to have our jar files in lib_managed dir.
|
||||
// This way we'll have the right path when we import
|
||||
// into eclipse.
|
||||
retrieveManaged := true
|
||||
|
||||
libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.8" % "test"
|
||||
libraryDependencies += "org.scala-lang.modules" %% "scala-xml" % "2.0.0"
|
||||
|
||||
testOptions in Test += Tests.Argument(TestFrameworks.Specs2, "html", "console", "junitxml")
|
||||
|
||||
testOptions in Test += Tests.Argument(TestFrameworks.ScalaTest, "-h", "target/scalatest-reports")
|
||||
|
||||
Seq(Revolver.settings: _*)
|
||||
lazy val bbbAppsAkka = (project in file(".")).settings(name := "bbb-apps-akka", libraryDependencies ++= Dependencies.runtime).settings(compileSettings)
|
||||
|
||||
// See https://github.com/scala-ide/scalariform
|
||||
// Config file is in ./.scalariform.conf
|
||||
scalariformAutoformat := true
|
||||
|
||||
scalaVersion := "2.13.9"
|
||||
//-----------
|
||||
// Packaging
|
||||
//
|
||||
// Reference:
|
||||
// https://github.com/muuki88/sbt-native-packager-examples/tree/master/akka-server-app
|
||||
// http://www.scala-sbt.org/sbt-native-packager/index.html
|
||||
//-----------
|
||||
mainClass := Some("org.bigbluebutton.Boot")
|
||||
|
||||
maintainer in Linux := "Richard Alam <ritzalam@gmail.com>"
|
||||
|
||||
packageSummary in Linux := "BigBlueButton Apps (Akka)"
|
||||
|
||||
packageDescription := """BigBlueButton Core Apps in Akka."""
|
||||
|
||||
val user = "bigbluebutton"
|
||||
|
||||
val group = "bigbluebutton"
|
||||
|
||||
// user which will execute the application
|
||||
daemonUser in Linux := user
|
||||
|
||||
// group which will execute the application
|
||||
daemonGroup in Linux := group
|
||||
|
||||
javaOptions in Universal ++= Seq("-J-Xms130m", "-J-Xmx256m", "-Dconfig.file=/etc/bigbluebutton/bbb-apps-akka.conf", "-Dlogback.configurationFile=conf/logback.xml")
|
||||
|
||||
debianPackageDependencies in Debian ++= Seq("java11-runtime-headless", "bash")
|
13
akka-bbb-apps/deploy.sh
Executable file
13
akka-bbb-apps/deploy.sh
Executable file
@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env bash
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
sudo service bbb-apps-akka stop
|
||||
sbt debian:packageBin
|
||||
sudo dpkg -i target/bbb-apps-akka_*.deb
|
||||
echo ''
|
||||
echo ''
|
||||
echo '----------------'
|
||||
echo 'bbb-apps-akka updated'
|
||||
|
||||
sudo service bbb-apps-akka start
|
||||
echo 'starting service bbb-apps-akka'
|
91
akka-bbb-apps/project/Dependencies.scala
Executable file
91
akka-bbb-apps/project/Dependencies.scala
Executable file
@ -0,0 +1,91 @@
|
||||
package org.bigbluebutton.build
|
||||
|
||||
import sbt._
|
||||
import Keys._
|
||||
|
||||
object Dependencies {
|
||||
|
||||
object Versions {
|
||||
// Scala
|
||||
val scala = "2.13.9"
|
||||
val junit = "4.12"
|
||||
val junitInterface = "0.11"
|
||||
val scalactic = "3.0.8"
|
||||
|
||||
// Libraries
|
||||
val akkaVersion = "2.6.17"
|
||||
val akkaHttpVersion = "10.2.7"
|
||||
val gson = "2.8.9"
|
||||
val jackson = "2.13.0"
|
||||
val logback = "1.2.10"
|
||||
val quicklens = "1.7.5"
|
||||
val spray = "1.3.6"
|
||||
|
||||
// Apache Commons
|
||||
val lang = "3.12.0"
|
||||
val codec = "1.15"
|
||||
|
||||
// BigBlueButton
|
||||
val bbbCommons = "0.0.22-SNAPSHOT"
|
||||
|
||||
// Test
|
||||
val scalaTest = "3.2.11"
|
||||
val mockito = "2.23.0"
|
||||
val akkaTestKit = "2.6.0"
|
||||
}
|
||||
|
||||
object Compile {
|
||||
val scalaLibrary = "org.scala-lang" % "scala-library" % Versions.scala
|
||||
val scalaCompiler = "org.scala-lang" % "scala-compiler" % Versions.scala
|
||||
|
||||
val akkaActor = "com.typesafe.akka" % "akka-actor_2.13" % Versions.akkaVersion
|
||||
val akkaSl4fj = "com.typesafe.akka" % "akka-slf4j_2.13" % Versions.akkaVersion
|
||||
|
||||
val googleGson = "com.google.code.gson" % "gson" % Versions.gson
|
||||
val jacksonModule = "com.fasterxml.jackson.module" %% "jackson-module-scala" % Versions.jackson
|
||||
val quicklens = "com.softwaremill.quicklens" %% "quicklens" % Versions.quicklens
|
||||
val logback = "ch.qos.logback" % "logback-classic" % Versions.logback
|
||||
val commonsCodec = "commons-codec" % "commons-codec" % Versions.codec
|
||||
val sprayJson = "io.spray" % "spray-json_2.13" % Versions.spray
|
||||
|
||||
val akkaStream = "com.typesafe.akka" %% "akka-stream" % Versions.akkaVersion
|
||||
val akkaHttp = "com.typesafe.akka" %% "akka-http" % Versions.akkaHttpVersion
|
||||
val akkaHttpSprayJson = "com.typesafe.akka" %% "akka-http-spray-json" % Versions.akkaHttpVersion
|
||||
|
||||
val apacheLang = "org.apache.commons" % "commons-lang3" % Versions.lang
|
||||
|
||||
val bbbCommons = "org.bigbluebutton" % "bbb-common-message_2.13" % Versions.bbbCommons
|
||||
}
|
||||
|
||||
object Test {
|
||||
val scalaTest = "org.scalatest" %% "scalatest" % Versions.scalaTest % "test"
|
||||
// val junit = "junit" % "junit" % Versions.junit % "test"
|
||||
val mockitoCore = "org.mockito" % "mockito-core" % Versions.mockito % "test"
|
||||
val scalactic = "org.scalactic" % "scalactic_2.13" % Versions.scalactic % "test"
|
||||
val akkaTestKit = "com.typesafe.akka" %% "akka-testkit" % Versions.akkaTestKit % "test"
|
||||
}
|
||||
|
||||
val testing = Seq(
|
||||
Test.scalaTest,
|
||||
// Test.junit,
|
||||
Test.mockitoCore,
|
||||
Test.scalactic,
|
||||
Test.akkaTestKit)
|
||||
|
||||
val runtime = Seq(
|
||||
Compile.scalaLibrary,
|
||||
Compile.scalaCompiler,
|
||||
Compile.akkaActor,
|
||||
Compile.akkaSl4fj,
|
||||
Compile.akkaStream,
|
||||
Compile.googleGson,
|
||||
Compile.jacksonModule,
|
||||
Compile.quicklens,
|
||||
Compile.logback,
|
||||
Compile.commonsCodec,
|
||||
Compile.sprayJson,
|
||||
Compile.apacheLang,
|
||||
Compile.akkaHttp,
|
||||
Compile.akkaHttpSprayJson,
|
||||
Compile.bbbCommons) ++ testing
|
||||
}
|
1
akka-bbb-apps/project/build.properties
Executable file
1
akka-bbb-apps/project/build.properties
Executable file
@ -0,0 +1 @@
|
||||
sbt.version=1.6.2
|
1
akka-bbb-apps/project/packager.sbt
Executable file
1
akka-bbb-apps/project/packager.sbt
Executable file
@ -0,0 +1 @@
|
||||
|
11
akka-bbb-apps/project/plugins.sbt
Executable file
11
akka-bbb-apps/project/plugins.sbt
Executable file
@ -0,0 +1,11 @@
|
||||
addSbtPlugin("io.spray" % "sbt-revolver" % "0.9.1")
|
||||
|
||||
addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "5.2.4")
|
||||
|
||||
addSbtPlugin("org.scalariform" % "sbt-scalariform" % "1.8.2")
|
||||
|
||||
addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.3.15")
|
||||
|
||||
addSbtPlugin("net.vonbuchholtz" % "sbt-dependency-check" % "0.2.9")
|
||||
|
||||
addSbtPlugin("org.scalastyle" %% "scalastyle-sbt-plugin" % "1.0.0")
|
14
akka-bbb-apps/run-dev.sh
Executable file
14
akka-bbb-apps/run-dev.sh
Executable file
@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
sudo service bbb-apps-akka stop
|
||||
|
||||
rm -rf src/main/resources
|
||||
cp -R src/universal/conf src/main/resources
|
||||
|
||||
#Set correct sharedSecret and bbbWebAPI
|
||||
sudo sed -i "s/sharedSecret = \"changeme\"/sharedSecret = \"$(sudo bbb-conf --salt | grep Secret: | cut -d ' ' -f 6)\"/g" src/main/resources/application.conf
|
||||
sudo sed -i "s/bbbWebAPI = \"https:\/\/192.168.23.33\/bigbluebutton\/api\"/bbbWebAPI = \"https:\/\/$(hostname -f)\/bigbluebutton\/api\"/g" src/main/resources/application.conf
|
||||
|
||||
#sbt update - Resolves and retrieves external dependencies, more details in https://www.scala-sbt.org/1.x/docs/Command-Line-Reference.html
|
||||
#sbt ~reStart (instead of run) - run with "triggered restart" mode, more details in #https://github.com/spray/sbt-revolver
|
||||
exec sbt update ~reStart
|
6
akka-bbb-apps/run.sh
Executable file
6
akka-bbb-apps/run.sh
Executable file
@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
sbt clean stage
|
||||
sudo service bbb-apps-akka stop
|
||||
cd target/universal/stage
|
||||
exec ./bin/bbb-apps-akka
|
39
akka-bbb-apps/scala/BreakoutRoom.sc
Normal file
39
akka-bbb-apps/scala/BreakoutRoom.sc
Normal file
@ -0,0 +1,39 @@
|
||||
import com.google.gson.Gson
|
||||
import org.bigbluebutton.messages.BreakoutRoomJoinURL
|
||||
|
||||
object BreakoutRoom {
|
||||
val gson = new Gson //> gson : com.google.gson.Gson = {serializeNulls:falsefactories:[Factory[typeH
|
||||
//| ierarchy=com.google.gson.JsonElement,adapter=com.google.gson.internal.bind.T
|
||||
//| ypeAdapters$29@6979e8cb], com.google.gson.internal.bind.ObjectTypeAdapter$1@
|
||||
//| 763d9750, com.google.gson.internal.Excluder@5c0369c4, Factory[type=java.lang
|
||||
//| .String,adapter=com.google.gson.internal.bind.TypeAdapters$16@2be94b0f], Fac
|
||||
//| tory[type=java.lang.Integer+int,adapter=com.google.gson.internal.bind.TypeAd
|
||||
//| apters$7@d70c109], Factory[type=java.lang.Boolean+boolean,adapter=com.google
|
||||
//| .gson.internal.bind.TypeAdapters$3@17ed40e0], Factory[type=java.lang.Byte+by
|
||||
//| te,adapter=com.google.gson.internal.bind.TypeAdapters$5@50675690], Factory[t
|
||||
//| ype=java.lang.Short+short,adapter=com.google.gson.internal.bind.TypeAdapters
|
||||
//| $6@31b7dea0], Factory[type=java.lang.Long+long,adapter=com.google.gson.inter
|
||||
//| nal.bind.TypeAdapters$11@3ac42916], Factory[type=java.lang.Double+double,ada
|
||||
//| pter=com.google.gson.Gso
|
||||
//| Output exceeds cutoff limit.
|
||||
|
||||
val string = "{\"payload\":{\"redirectJoinUrl\":\"alink\",\"breakoutMeetingId\":\"4455e780b6f62cd5fcf09367aef62d9bc5108375-1479728671031\",\"redirectToHtml5JoinUrl\":\"http://bbb.riadvice.com/bigbluebutton/api/join?fullName\u003dOpera\u0026isBreakout\u003dtrue\u0026meetingID\u003d4455e780b6f62cd5fcf09367aef62d9bc5108375-1479728671031\u0026password\u003dmp\u0026redirect\u003dfalse\u0026userID\u003d6pt0vfeaxdze_1-1\u0026checksum\u003d51c4a1398b88170c25f1a71521bca604e784ab23\",\"parentMeetingId\":\"183f0bf3a0982a127bdb8161e0c44eb696b3e75c-1479728593178\",\"userId\":\"6pt0vfeaxdze_1\"},\"header\":{\"name\":\"BreakoutRoomJoinURL\",\"version\":\"0.0.1\",\"current_time\":1479728673586,\"timestamp\":8549632}}"
|
||||
//> string : String = {"payload":{"redirectJoinUrl":"alink","breakoutMeetingId"
|
||||
//| :"4455e780b6f62cd5fcf09367aef62d9bc5108375-1479728671031","noRedirectJoinUrl
|
||||
//| ":"http://bbb.riadvice.com/bigbluebutton/api/join?fullName=Opera&isBreakout=
|
||||
//| true&meetingID=4455e780b6f62cd5fcf09367aef62d9bc5108375-1479728671031&passwo
|
||||
//| rd=mp&redirect=false&userID=6pt0vfeaxdze_1-1&checksum=51c4a1398b88170c25f1a7
|
||||
//| 1521bca604e784ab23","parentMeetingId":"183f0bf3a0982a127bdb8161e0c44eb696b3e
|
||||
//| 75c-1479728593178","userId":"6pt0vfeaxdze_1"},"header":{"name":"BreakoutRoom
|
||||
//| JoinURL","version":"0.0.1","current_time":1479728673586,"timestamp":8549632}
|
||||
//| }
|
||||
val brjum: BreakoutRoomJoinURL = gson.fromJson(string, classOf[BreakoutRoomJoinURL])
|
||||
//> brjum : org.bigbluebutton.messages.BreakoutRoomJoinURL = org.bigbluebutton.
|
||||
//| messages.BreakoutRoomJoinURL@3327bd23
|
||||
|
||||
println(brjum.payload.userId) //> 6pt0vfeaxdze_1
|
||||
println(brjum.payload.parentMeetingId) //> 183f0bf3a0982a127bdb8161e0c44eb696b3e75c-1479728593178
|
||||
println(brjum.payload.breakoutMeetingId) //> 4455e780b6f62cd5fcf09367aef62d9bc5108375-1479728671031
|
||||
println(brjum.payload.redirectJoinURL) //> null
|
||||
println(brjum.payload.redirectToHtml5JoinURL) //> null
|
||||
}
|
9
akka-bbb-apps/scala/Collections.sc
Executable file
9
akka-bbb-apps/scala/Collections.sc
Executable file
@ -0,0 +1,9 @@
|
||||
import scala.collection.mutable
|
||||
|
||||
object Collections {
|
||||
val messages = new mutable.Queue[String]()
|
||||
messages += "foo"
|
||||
messages += "bar"
|
||||
messages += "baz"
|
||||
messages.foreach(f => println(f))
|
||||
}
|
122
akka-bbb-apps/scala/JsonTest.sc
Executable file
122
akka-bbb-apps/scala/JsonTest.sc
Executable file
@ -0,0 +1,122 @@
|
||||
import org.bigbluebutton.core.api._
|
||||
import scala.util.{ Try, Success, Failure }
|
||||
import org.bigbluebutton.core.JsonMessageDecoder
|
||||
import org.bigbluebutton.messages.BreakoutRoomsList
|
||||
import org.bigbluebutton.messages.payload.BreakoutRoomsListPayload
|
||||
import java.util.ArrayList
|
||||
import org.bigbluebutton.core.messaging.Util
|
||||
import org.bigbluebutton.messages.payload.BreakoutRoomPayload
|
||||
import org.bigbluebutton.core.pubsub.senders.MeetingMessageToJsonConverter
|
||||
import spray.json._
|
||||
import DefaultJsonProtocol._
|
||||
import com.google.gson.JsonArray
|
||||
import scala.collection.mutable.ListBuffer
|
||||
|
||||
object JsonTest {
|
||||
import org.bigbluebutton.core.UserMessagesProtocol._
|
||||
import spray.json._
|
||||
|
||||
println("Welcome to the Scala worksheet") //> Welcome to the Scala worksheet
|
||||
|
||||
val xroom1 = new BreakoutRoomInPayload("foo", Vector("a", "b", "c"))
|
||||
//> xroom1 : org.bigbluebutton.core.api.BreakoutRoomInPayload = BreakoutRoomInP
|
||||
//| ayload(foo,Vector(a, b, c))
|
||||
val xroom2 = new BreakoutRoomInPayload("bar", Vector("x", "y", "z"))
|
||||
//> xroom2 : org.bigbluebutton.core.api.BreakoutRoomInPayload = BreakoutRoomInP
|
||||
//| ayload(bar,Vector(x, y, z))
|
||||
val xroom3 = new BreakoutRoomInPayload("baz", Vector("q", "r", "s"))
|
||||
//> xroom3 : org.bigbluebutton.core.api.BreakoutRoomInPayload = BreakoutRoomInP
|
||||
//| ayload(baz,Vector(q, r, s))
|
||||
|
||||
val xmsg = new CreateBreakoutRooms("test-meeting", 10, false, Vector(xroom1, xroom2, xroom3))
|
||||
//> xmsg : org.bigbluebutton.core.api.CreateBreakoutRooms = CreateBreakoutRoom
|
||||
//| s(test-meeting,10,false,Vector(BreakoutRoomInPayload(foo,Vector(a, b, c)),
|
||||
//| BreakoutRoomInPayload(bar,Vector(x, y, z)), BreakoutRoomInPayload(baz,Vecto
|
||||
//| r(q, r, s))))
|
||||
|
||||
val xjsonAst = xmsg.toJson //> xjsonAst : spray.json.JsValue = {"meetingId":"test-meeting","durationInMin
|
||||
//| utes":10,"record":false,"rooms":[{"name":"foo","users":["a","b","c"]},{"nam
|
||||
//| e":"bar","users":["x","y","z"]},{"name":"baz","users":["q","r","s"]}]}
|
||||
val xjson = xjsonAst.asJsObject //> xjson : spray.json.JsObject = {"meetingId":"test-meeting","durationInMinut
|
||||
//| es":10,"record":false,"rooms":[{"name":"foo","users":["a","b","c"]},{"name"
|
||||
//| :"bar","users":["x","y","z"]},{"name":"baz","users":["q","r","s"]}]}
|
||||
val meetingId = for {
|
||||
meetingId <- xjson.fields.get("meetingId")
|
||||
} yield meetingId //> meetingId : Option[spray.json.JsValue] = Some("test-meeting")
|
||||
|
||||
val cbrm = """
|
||||
{"header":{"name":"CreateBreakoutRoomsRequest"},"payload":{"meetingId":"abc123","rooms":[{"name":"room1","users":["Tidora","Nidora","Tinidora"]},{"name":"room2","users":["Jose","Wally","Paolo"]},{"name":"room3","users":["Alden","Yaya Dub"]}],"durationInMinutes":20}}
|
||||
""" //> cbrm : String = "
|
||||
//| {"header":{"name":"CreateBreakoutRoomsRequest"},"payload":{"meetingId":"a
|
||||
//| bc123","rooms":[{"name":"room1","users":["Tidora","Nidora","Tinidora"]},{"n
|
||||
//| ame":"room2","users":["Jose","Wally","Paolo"]},{"name":"room3","users":["Al
|
||||
//| den","Yaya Dub"]}],"durationInMinutes":20}}
|
||||
//| "
|
||||
|
||||
JsonMessageDecoder.decode(cbrm) //> res0: Option[org.bigbluebutton.core.api.InMessage] = None
|
||||
val rbju = """
|
||||
{"header":{"name":"RequestBreakoutJoinURL"},"payload":{"userId":"id6pa5t8m1c9_1","meetingId":"183f0bf3a0982a127bdb8161e0c44eb696b3e75c-1452692983357","breakoutId":"183f0bf3a0982a127bdb8161e0c44eb696b3e75c-1452692983357-2"}}
|
||||
""" //> rbju : String = "
|
||||
//| {"header":{"name":"RequestBreakoutJoinURL"},"payload":{"userId":"id6pa5t8
|
||||
//| m1c9_1","meetingId":"183f0bf3a0982a127bdb8161e0c44eb696b3e75c-1452692983357
|
||||
//| ","breakoutId":"183f0bf3a0982a127bdb8161e0c44eb696b3e75c-1452692983357-2"}}
|
||||
//|
|
||||
//| "
|
||||
|
||||
JsonMessageDecoder.decode(rbju) //> res1: Option[org.bigbluebutton.core.api.InMessage] = Some(RequestBreakoutJo
|
||||
//| inURLInMessage(183f0bf3a0982a127bdb8161e0c44eb696b3e75c-1452692983357,183f0
|
||||
//| bf3a0982a127bdb8161e0c44eb696b3e75c-1452692983357-2,id6pa5t8m1c9_1))
|
||||
|
||||
val brl = """
|
||||
{"payload":{"meetingId":"183f0bf3a0982a127bdb8161e0c44eb696b3e75c-1452849036428","rooms":{"startIndex":0,"endIndex":0,"focus":0,"dirty":false,"depth":0}},"header":{"timestamp":33619724,"name":"BreakoutRoomsList","current_time":1452849043547,"version":"0.0.1"}}
|
||||
""" //> brl : String = "
|
||||
//| {"payload":{"meetingId":"183f0bf3a0982a127bdb8161e0c44eb696b3e75c-1452849
|
||||
//| 036428","rooms":{"startIndex":0,"endIndex":0,"focus":0,"dirty":false,"depth
|
||||
//| ":0}},"header":{"timestamp":33619724,"name":"BreakoutRoomsList","current_ti
|
||||
//| me":1452849043547,"version":"0.0.1"}}
|
||||
//| "
|
||||
|
||||
val brb = new BreakoutRoomBody("Breakout Room", "br-id-1");
|
||||
//> brb : org.bigbluebutton.core.api.BreakoutRoomBody = BreakoutRoomBody(Break
|
||||
//| out Room,br-id-1)
|
||||
|
||||
val jsObj = JsObject(
|
||||
"name" -> JsString(brb.name),
|
||||
"breakoutId" -> JsString(brb.breakoutId)) //> jsObj : spray.json.JsObject = {"name":"Breakout Room","breakoutId":"br-id-
|
||||
//| 1"}
|
||||
|
||||
val vector = Vector(jsObj) //> vector : scala.collection.immutable.Vector[spray.json.JsObject] = Vector({
|
||||
//| "name":"Breakout Room","breakoutId":"br-id-1"})
|
||||
|
||||
val brlum = new BreakoutRoomsListOutMessage("main-meeting-1", Vector(brb), false)
|
||||
//> brlum : org.bigbluebutton.core.api.BreakoutRoomsListOutMessage = BreakoutR
|
||||
//| oomsListOutMessage(main-meeting-1,Vector(BreakoutRoomBody(Breakout Room,br-
|
||||
//| id-1)),false)
|
||||
var roomsJsVector: ListBuffer[JsObject] = new ListBuffer[JsObject]()
|
||||
//> roomsJsVector : scala.collection.mutable.ListBuffer[spray.json.JsObject] =
|
||||
//| ListBuffer()
|
||||
brlum.rooms.foreach { r =>
|
||||
roomsJsVector.append(JsObject("name" -> JsString(r.name), "breakoutId" -> JsString(r.breakoutId)))
|
||||
}
|
||||
|
||||
roomsJsVector.length //> res2: Int = 1
|
||||
|
||||
val jsonAst = List(1, 2, 3).toJson //> jsonAst : spray.json.JsValue = [1,2,3]
|
||||
|
||||
roomsJsVector.toList.toJson //> res3: spray.json.JsValue = [{"name":"Breakout Room","breakoutId":"br-id-1"}
|
||||
//| ]
|
||||
JsArray(roomsJsVector.toVector) //> res4: spray.json.JsArray = [{"name":"Breakout Room","breakoutId":"br-id-1"}
|
||||
//| ]
|
||||
|
||||
MeetingMessageToJsonConverter.breakoutRoomsListOutMessageToJson(brlum);
|
||||
//> res5: String = {"payload":{"meetingId":"main-meeting-1","roomsReady":false,
|
||||
//| "rooms":[{"name":"Breakout Room","breakoutId":"br-id-1"}]},"header":{"times
|
||||
//| tamp":9652644,"name":"BreakoutRoomsList","current_time":1471504366540,"vers
|
||||
//| ion":"0.0.1"}}
|
||||
|
||||
// JsonMessageDecoder.unmarshall(cbrm) match {
|
||||
// case Success(validMsg) => println(validMsg)
|
||||
// case Failure(ex) => println("Unhandled message: [{}]")
|
||||
// }
|
||||
|
||||
}
|
77
akka-bbb-apps/scala/Test2.sc
Executable file
77
akka-bbb-apps/scala/Test2.sc
Executable file
@ -0,0 +1,77 @@
|
||||
import scala.collection.immutable.StringOps
|
||||
import java.net.URLEncoder
|
||||
import scala.collection._
|
||||
|
||||
object Test2 {
|
||||
println("Welcome to the Scala worksheet") //> Welcome to the Scala worksheet
|
||||
|
||||
val userId = new StringOps("abc_12") //> userId : scala.collection.immutable.StringOps = abc_12
|
||||
val s2 = userId.split('_') //> s2 : Array[String] = Array(abc, 12)
|
||||
val s1 = if (s2.length == 2) s2(0) else userId //> s1 : Comparable[String] = abc
|
||||
|
||||
def sortParam(params: mutable.Map[String, String]):SortedSet[String] = {
|
||||
collection.immutable.SortedSet[String]() ++ params.keySet
|
||||
} //> sortParam: (params: scala.collection.mutable.Map[String,String])scala.collec
|
||||
//| tion.SortedSet[String]
|
||||
|
||||
def createBaseString(params: mutable.Map[String, String]): String = {
|
||||
val csbuf = new StringBuffer()
|
||||
var keys = sortParam(params)
|
||||
|
||||
var first = true;
|
||||
for (key <- keys) {
|
||||
for (value <- params.get(key)) {
|
||||
if (first) {
|
||||
first = false;
|
||||
} else {
|
||||
csbuf.append("&");
|
||||
}
|
||||
|
||||
csbuf.append(key);
|
||||
csbuf.append("=");
|
||||
csbuf.append(value);
|
||||
}
|
||||
}
|
||||
|
||||
return csbuf.toString();
|
||||
} //> createBaseString: (params: scala.collection.mutable.Map[String,String])Strin
|
||||
//| g
|
||||
|
||||
def urlEncode(s: String): String = {
|
||||
URLEncoder.encode(s, "UTF-8");
|
||||
} //> urlEncode: (s: String)String
|
||||
|
||||
val baseString = "fullName=User+4621018&isBreakout=true&meetingID=random-1853792&password=mp&redirect=true"
|
||||
//> baseString : String = fullName=User+4621018&isBreakout=true&meetingID=rand
|
||||
//| om-1853792&password=mp&redirect=true
|
||||
|
||||
val params = new collection.mutable.HashMap[String, String]
|
||||
//> params : scala.collection.mutable.HashMap[String,String] = Map()
|
||||
params += "fullName" -> urlEncode("User 4621018")
|
||||
//> res0: Test2.params.type = Map(fullName -> User+4621018)
|
||||
params += "isBreakout" -> urlEncode("true") //> res1: Test2.params.type = Map(fullName -> User+4621018, isBreakout -> true)
|
||||
//|
|
||||
params += "meetingID" -> urlEncode("random-1853792")
|
||||
//> res2: Test2.params.type = Map(fullName -> User+4621018, isBreakout -> true,
|
||||
//| meetingID -> random-1853792)
|
||||
params += "password" -> urlEncode("mp") //> res3: Test2.params.type = Map(fullName -> User+4621018, isBreakout -> true,
|
||||
//| meetingID -> random-1853792, password -> mp)
|
||||
params += "redirect" -> urlEncode("true") //> res4: Test2.params.type = Map(fullName -> User+4621018, isBreakout -> true,
|
||||
//| meetingID -> random-1853792, redirect -> true, password -> mp)
|
||||
val keys = sortParam(params) //> keys : scala.collection.SortedSet[String] = TreeSet(fullName, isBreakout,
|
||||
//| meetingID, password, redirect)
|
||||
|
||||
val result = createBaseString(params) //> result : String = fullName=User+4621018&isBreakout=true&meetingID=random-1
|
||||
//| 853792&password=mp&redirect=true
|
||||
|
||||
val between = Set("xab", "bc")
|
||||
val u2 = Set("bc", "xab")
|
||||
val u3 = u2 + "zxc"
|
||||
val foo = between subsetOf(u2)
|
||||
|
||||
val id = between.toSeq.sorted.mkString("-")
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
126
akka-bbb-apps/scala/TestWorksheet.sc
Executable file
126
akka-bbb-apps/scala/TestWorksheet.sc
Executable file
@ -0,0 +1,126 @@
|
||||
import scala.collection.immutable.StringOps
|
||||
import org.bigbluebutton.core.apps.Answer
|
||||
import org.bigbluebutton.core.apps.Question
|
||||
|
||||
object TestWorksheet {
|
||||
println("Welcome to the Scala worksheet") //> Welcome to the Scala worksheet
|
||||
|
||||
|
||||
val YesNoPollType = "YN" //> YesNoPollType : String = YN
|
||||
val TrueFalsePollType = "TF" //> TrueFalsePollType : String = TF
|
||||
val LetterPollType = "A-" //> LetterPollType : String = A-
|
||||
val NumberPollType = "1-" //> NumberPollType : String = 1-
|
||||
|
||||
val LetterArray = Array("A", "B", "C", "D", "E", "F")
|
||||
//> LetterArray : Array[String] = Array(A, B, C, D, E, F)
|
||||
val NumberArray = Array("1", "2", "3", "4", "5", "6")
|
||||
//> NumberArray : Array[String] = Array(1, 2, 3, 4, 5, 6)
|
||||
|
||||
def processYesNoPollType(qType: String):Question = {
|
||||
val answers = new Array[Answer](2)
|
||||
answers(0) = new Answer(0, "N", Some("No"))
|
||||
answers(1) = new Answer(1, "Y", Some("Yes"))
|
||||
|
||||
new Question(0, YesNoPollType, false, None, answers)
|
||||
} //> processYesNoPollType: (qType: String)org.bigbluebutton.core.apps.Question
|
||||
|
||||
def processTrueFalsePollType(qType: String):Question = {
|
||||
val answers = new Array[Answer](2)
|
||||
|
||||
answers(0) = new Answer(0, "F", Some("False"))
|
||||
answers(1) = new Answer(1, "T", Some("True"))
|
||||
|
||||
new Question(0, TrueFalsePollType, false, None, answers)
|
||||
} //> processTrueFalsePollType: (qType: String)org.bigbluebutton.core.apps.Questio
|
||||
//| n
|
||||
|
||||
|
||||
def processLetterPollType(qType: String, multiResponse: Boolean):Option[Question] = {
|
||||
val q = qType.split('-')
|
||||
val numQs = q(1).toInt
|
||||
|
||||
var questionOption: Option[Question] = None
|
||||
|
||||
if (numQs > 0 && numQs <= 7) {
|
||||
val answers = new Array[Answer](numQs)
|
||||
var i = 0
|
||||
for ( i <- 0 until numQs ) {
|
||||
answers(i) = new Answer(i, LetterArray(i), Some(LetterArray(i)))
|
||||
val question = new Question(0, LetterPollType, multiResponse, None, answers)
|
||||
questionOption = Some(question)
|
||||
}
|
||||
}
|
||||
|
||||
questionOption
|
||||
} //> processLetterPollType: (qType: String, multiResponse: Boolean)Option[org.bi
|
||||
//| gbluebutton.core.apps.Question]
|
||||
|
||||
def processNumberPollType(qType: String, multiResponse: Boolean):Option[Question] = {
|
||||
val q = qType.split('-')
|
||||
val numQs = q(1).toInt
|
||||
|
||||
var questionOption: Option[Question] = None
|
||||
|
||||
if (numQs > 0 && numQs <= 7) {
|
||||
val answers = new Array[Answer](numQs)
|
||||
var i = 0
|
||||
for ( i <- 0 until numQs ) {
|
||||
answers(i) = new Answer(i, NumberArray(i), Some(NumberArray(i)))
|
||||
val question = new Question(0, NumberPollType, multiResponse, None, answers)
|
||||
questionOption = Some(question)
|
||||
}
|
||||
}
|
||||
questionOption
|
||||
} //> processNumberPollType: (qType: String, multiResponse: Boolean)Option[org.bi
|
||||
//| gbluebutton.core.apps.Question]
|
||||
|
||||
def createQuestion(qType: String):Option[Question] = {
|
||||
val qt = qType.toUpperCase()
|
||||
var questionOption: Option[Question] = None
|
||||
if (qt.matches(YesNoPollType)) {
|
||||
questionOption = Some(processYesNoPollType(qt))
|
||||
} else if (qt.matches(TrueFalsePollType)) {
|
||||
questionOption = Some(processTrueFalsePollType(qt))
|
||||
} else if (qt.startsWith(LetterPollType)) {
|
||||
questionOption = processLetterPollType(qt, false)
|
||||
} else if (qt.startsWith(NumberPollType)) {
|
||||
processNumberPollType(qt, false)
|
||||
} else {
|
||||
questionOption = None
|
||||
}
|
||||
|
||||
questionOption
|
||||
} //> createQuestion: (qType: String)Option[org.bigbluebutton.core.apps.Question]
|
||||
//|
|
||||
|
||||
def determineQType3(qType: String) {
|
||||
val qt = qType.toUpperCase()
|
||||
|
||||
if (qt.matches(YesNoPollType)) {
|
||||
println("YN")
|
||||
} else if (qt.matches("TF")) {
|
||||
println(TrueFalsePollType)
|
||||
} else if (qt.startsWith(LetterPollType)) {
|
||||
println("A5")
|
||||
processLetterPollType(qt, false)
|
||||
} else if (qt.startsWith(NumberPollType)) {
|
||||
println("1")
|
||||
processNumberPollType(qt, false)
|
||||
} else {
|
||||
println("No Match for [" + qType + "]")
|
||||
}
|
||||
} //> determineQType3: (qType: String)Unit
|
||||
|
||||
determineQType3("YN") //> YN
|
||||
determineQType3("YF") //> No Match for [YF]
|
||||
determineQType3("TF") //> TF
|
||||
determineQType3("A-5") //> A5
|
||||
determineQType3("1-5") //> 1
|
||||
|
||||
val list = new java.util.ArrayList[String]() //> list : java.util.ArrayList[String] = []
|
||||
list.add("Red") //> res0: Boolean = true
|
||||
list.add("Green") //> res1: Boolean = true
|
||||
list.add("Blue") //> res2: Boolean = true
|
||||
|
||||
|
||||
}
|
12
akka-bbb-apps/src/debian/DEBIAN/postrm
Normal file
12
akka-bbb-apps/src/debian/DEBIAN/postrm
Normal file
@ -0,0 +1,12 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
case "$1" in
|
||||
purge)
|
||||
# remove config file added/changed by preinst script
|
||||
OP_CONFIG=/etc/bigbluebutton/bbb-apps-akka.conf
|
||||
if [ -f "${OP_CONFIG}" ]; then
|
||||
rm "${OP_CONFIG}"
|
||||
fi
|
||||
esac
|
38
akka-bbb-apps/src/debian/DEBIAN/preinst
Normal file
38
akka-bbb-apps/src/debian/DEBIAN/preinst
Normal file
@ -0,0 +1,38 @@
|
||||
#!/bin/sh
|
||||
|
||||
write_config_file() {
|
||||
mkdir -p /etc/bigbluebutton
|
||||
|
||||
cat <<END > "${OP_CONFIG}"
|
||||
// include default config from upstream
|
||||
include "/usr/share/bbb-apps-akka/conf/application.conf"
|
||||
|
||||
// you can customize everything here. API endpoint and secret have to be changed
|
||||
// This file will not be overridden by packages
|
||||
|
||||
services {
|
||||
bbbWebAPI=${API}
|
||||
sharedSecret=${SECRET}
|
||||
}
|
||||
END
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
install|upgrade|1|2)
|
||||
|
||||
PACKAGE_CONFIG=/usr/share/bbb-apps-akka/conf/application.conf
|
||||
OP_CONFIG=/etc/bigbluebutton/bbb-apps-akka.conf
|
||||
# this is for upgrading packages from old packaging system where operators had
|
||||
# to change the package config file
|
||||
if [ ! -f "${OP_CONFIG}" -a -f "${PACKAGE_CONFIG}" ]; then
|
||||
API=$(grep bbbWebAPI "${PACKAGE_CONFIG}" | sed 's/[^=]*=\s*//')
|
||||
SECRET=$(grep sharedSecret "${PACKAGE_CONFIG}" | sed 's/[^=]*=\s*//')
|
||||
write_config_file
|
||||
fi
|
||||
# this is for fresh installs, where no config file exists
|
||||
if [ ! -f "${OP_CONFIG}" ]; then
|
||||
SECRET='"changeme"'
|
||||
API='"https://192.168.23.33/bigbluebutton/api"'
|
||||
write_config_file
|
||||
fi
|
||||
esac
|
128
akka-bbb-apps/src/main/scala/org/bigbluebutton/ApiService.scala
Executable file
128
akka-bbb-apps/src/main/scala/org/bigbluebutton/ApiService.scala
Executable file
@ -0,0 +1,128 @@
|
||||
package org.bigbluebutton
|
||||
|
||||
import akka.http.scaladsl.model._
|
||||
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
|
||||
import akka.http.scaladsl.server.Directives._
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.service.{ HealthzService, MeetingInfoService, PubSubReceiveStatus, PubSubSendStatus, RecordingDBSendStatus }
|
||||
import spray.json._
|
||||
import scala.concurrent._
|
||||
import ExecutionContext.Implicits.global
|
||||
|
||||
case class HealthResponse(
|
||||
isHealthy: Boolean,
|
||||
pubsubSendStatus: PubSubSendStatus,
|
||||
pubsubReceiveStatus: PubSubReceiveStatus,
|
||||
recordingDbStatus: RecordingDBSendStatus
|
||||
)
|
||||
|
||||
case class MeetingInfoResponse(
|
||||
meetingInfoResponse: Option[MeetingInfoAnalytics]
|
||||
)
|
||||
|
||||
case class MeetingInfoAnalytics(
|
||||
name: String,
|
||||
externalId: String,
|
||||
internalId: String,
|
||||
hasUserJoined: Boolean,
|
||||
isMeetingRecorded: Boolean,
|
||||
webcams: Webcam,
|
||||
audio: Audio,
|
||||
screenshare: Screenshare,
|
||||
users: List[Participant],
|
||||
presentation: PresentationInfo,
|
||||
breakoutRoom: BreakoutRoom
|
||||
)
|
||||
|
||||
trait JsonSupportProtocolHealthResponse extends SprayJsonSupport with DefaultJsonProtocol {
|
||||
implicit val pubSubSendStatusJsonFormat = jsonFormat2(PubSubSendStatus)
|
||||
implicit val pubSubReceiveStatusJsonFormat = jsonFormat2(PubSubReceiveStatus)
|
||||
implicit val recordingDbStatusJsonFormat = jsonFormat2(RecordingDBSendStatus)
|
||||
implicit val healthServiceJsonFormat = jsonFormat4(HealthResponse)
|
||||
}
|
||||
|
||||
trait JsonSupportProtocolMeetingInfoResponse extends SprayJsonSupport with DefaultJsonProtocol {
|
||||
implicit val meetingInfoUserJsonFormat = jsonFormat2(User)
|
||||
implicit val meetingInfoBroadcastJsonFormat = jsonFormat3(Broadcast)
|
||||
implicit val meetingInfoWebcamStreamJsonFormat = jsonFormat2(WebcamStream)
|
||||
implicit val meetingInfoWebcamJsonFormat = jsonFormat2(Webcam)
|
||||
|
||||
implicit val meetingInfoListenOnlyAudioJsonFormat = jsonFormat2(ListenOnlyAudio)
|
||||
implicit val meetingInfoTwoWayAudioJsonFormat = jsonFormat2(TwoWayAudio)
|
||||
implicit val meetingInfoPhoneAudioJsonFormat = jsonFormat2(PhoneAudio)
|
||||
implicit val meetingInfoAudioJsonFormat = jsonFormat4(Audio)
|
||||
|
||||
implicit val meetingInfoScreenshareStreamJsonFormat = jsonFormat2(ScreenshareStream)
|
||||
implicit val meetingInfoScreenshareJsonFormat = jsonFormat1(Screenshare)
|
||||
|
||||
implicit val meetingInfoPresentationInfoJsonFormat = jsonFormat2(PresentationInfo)
|
||||
implicit val meetingInfoBreakoutRoomJsonFormat = jsonFormat2(BreakoutRoom)
|
||||
|
||||
implicit val meetingInfoParticipantJsonFormat = jsonFormat3(Participant)
|
||||
implicit val meetingInfoAnalyticsJsonFormat = jsonFormat11(MeetingInfoAnalytics)
|
||||
implicit val meetingInfoResponseJsonFormat = jsonFormat1(MeetingInfoResponse)
|
||||
}
|
||||
|
||||
class ApiService(healthz: HealthzService, meetingInfoz: MeetingInfoService)
|
||||
extends JsonSupportProtocolHealthResponse
|
||||
with JsonSupportProtocolMeetingInfoResponse {
|
||||
|
||||
def routes =
|
||||
path("healthz") {
|
||||
get {
|
||||
val future = healthz.getHealthz()
|
||||
onSuccess(future) {
|
||||
case response =>
|
||||
if (response.isHealthy) {
|
||||
complete(
|
||||
StatusCodes.OK,
|
||||
HealthResponse(
|
||||
response.isHealthy,
|
||||
response.pubSubSendStatus,
|
||||
response.pubSubReceiveStatus,
|
||||
response.recordingDBSendStatus
|
||||
)
|
||||
)
|
||||
} else {
|
||||
complete(
|
||||
StatusCodes.ServiceUnavailable,
|
||||
HealthResponse(
|
||||
response.isHealthy,
|
||||
response.pubSubSendStatus,
|
||||
response.pubSubReceiveStatus,
|
||||
response.recordingDBSendStatus
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
} ~
|
||||
path("analytics") {
|
||||
parameter('meetingId.as[String]) { meetingId =>
|
||||
get {
|
||||
val meetingAnalyticsFuture = meetingInfoz.getAnalytics(meetingId)
|
||||
val entityFuture = meetingAnalyticsFuture.map { resp =>
|
||||
resp.optionMeetingInfoAnalytics match {
|
||||
case Some(_) =>
|
||||
HttpEntity(ContentTypes.`application/json`, resp.optionMeetingInfoAnalytics.get.toJson.prettyPrint)
|
||||
case None =>
|
||||
HttpEntity(ContentTypes.`application/json`, s"""{ "message": "No active meeting with ID $meetingId"}""".parseJson.prettyPrint)
|
||||
}
|
||||
}
|
||||
complete(entityFuture)
|
||||
}
|
||||
} ~
|
||||
get {
|
||||
val future = meetingInfoz.getAnalytics()
|
||||
val entityFuture = future.map { res =>
|
||||
res.optionMeetingsInfoAnalytics match {
|
||||
case Some(_) =>
|
||||
HttpEntity(ContentTypes.`application/json`, res.optionMeetingsInfoAnalytics.get.toJson.prettyPrint)
|
||||
case None =>
|
||||
HttpEntity(ContentTypes.`application/json`, """{ "message": "No active meetings"}""".parseJson.prettyPrint)
|
||||
}
|
||||
}
|
||||
complete(entityFuture)
|
||||
}
|
||||
}
|
||||
}
|
111
akka-bbb-apps/src/main/scala/org/bigbluebutton/Boot.scala
Executable file
111
akka-bbb-apps/src/main/scala/org/bigbluebutton/Boot.scala
Executable file
@ -0,0 +1,111 @@
|
||||
package org.bigbluebutton
|
||||
|
||||
import akka.actor.ActorSystem
|
||||
import akka.event.Logging
|
||||
import akka.http.scaladsl.Http
|
||||
import akka.stream.ActorMaterializer
|
||||
import org.bigbluebutton.common2.redis.{ MessageSender, RedisConfig, RedisPublisher }
|
||||
import org.bigbluebutton.core._
|
||||
import org.bigbluebutton.core.bus._
|
||||
import org.bigbluebutton.core.pubsub.senders.ReceivedJsonMsgHandlerActor
|
||||
import org.bigbluebutton.core2.AnalyticsActor
|
||||
import org.bigbluebutton.core2.FromAkkaAppsMsgSenderActor
|
||||
import org.bigbluebutton.endpoint.redis.AppsRedisSubscriberActor
|
||||
import org.bigbluebutton.endpoint.redis.{ RedisRecorderActor, ExportAnnotationsActor }
|
||||
import org.bigbluebutton.endpoint.redis.LearningDashboardActor
|
||||
import org.bigbluebutton.common2.bus.IncomingJsonMessageBus
|
||||
import org.bigbluebutton.service.{ HealthzService, MeetingInfoActor, MeetingInfoService }
|
||||
|
||||
object Boot extends App with SystemConfiguration {
|
||||
|
||||
implicit val system = ActorSystem("bigbluebutton-apps-system")
|
||||
implicit val materializer: ActorMaterializer = ActorMaterializer()
|
||||
implicit val executor = system.dispatcher
|
||||
|
||||
val logger = Logging(system, getClass)
|
||||
|
||||
val eventBus = new InMsgBusGW(new IncomingEventBusImp())
|
||||
|
||||
val outBus2 = new OutEventBus2
|
||||
val recordingEventBus = new RecordingEventBus
|
||||
|
||||
val outGW = new OutMessageGatewayImp(outBus2)
|
||||
|
||||
val redisPass = if (redisPassword != "") Some(redisPassword) else None
|
||||
val redisConfig = RedisConfig(redisHost, redisPort, redisPass, redisExpireKey)
|
||||
|
||||
val redisPublisher = new RedisPublisher(
|
||||
system,
|
||||
"BbbAppsAkkaPub",
|
||||
redisConfig
|
||||
)
|
||||
|
||||
val msgSender = new MessageSender(redisPublisher)
|
||||
val bbbMsgBus = new BbbMsgRouterEventBus
|
||||
|
||||
val healthzService = HealthzService(system)
|
||||
|
||||
val meetingInfoActorRef = system.actorOf(MeetingInfoActor.props())
|
||||
|
||||
outBus2.subscribe(meetingInfoActorRef, outBbbMsgMsgChannel)
|
||||
bbbMsgBus.subscribe(meetingInfoActorRef, analyticsChannel)
|
||||
|
||||
val meetingInfoService = MeetingInfoService(system, meetingInfoActorRef)
|
||||
|
||||
val apiService = new ApiService(healthzService, meetingInfoService)
|
||||
|
||||
val redisRecorderActor = system.actorOf(
|
||||
RedisRecorderActor.props(system, redisConfig, healthzService),
|
||||
"redisRecorderActor"
|
||||
)
|
||||
|
||||
val exportAnnotationsActor = system.actorOf(
|
||||
ExportAnnotationsActor.props(system, redisConfig, healthzService),
|
||||
"exportAnnotationsActor"
|
||||
)
|
||||
|
||||
val learningDashboardActor = system.actorOf(
|
||||
LearningDashboardActor.props(system, outGW),
|
||||
"LearningDashboardActor"
|
||||
)
|
||||
|
||||
recordingEventBus.subscribe(redisRecorderActor, outMessageChannel)
|
||||
val incomingJsonMessageBus = new IncomingJsonMessageBus
|
||||
|
||||
val fromAkkaAppsMsgSenderActorRef = system.actorOf(FromAkkaAppsMsgSenderActor.props(msgSender))
|
||||
|
||||
val analyticsActorRef = system.actorOf(AnalyticsActor.props(analyticsIncludeChat))
|
||||
outBus2.subscribe(fromAkkaAppsMsgSenderActorRef, outBbbMsgMsgChannel)
|
||||
outBus2.subscribe(redisRecorderActor, recordServiceMessageChannel)
|
||||
outBus2.subscribe(exportAnnotationsActor, outBbbMsgMsgChannel)
|
||||
|
||||
outBus2.subscribe(analyticsActorRef, outBbbMsgMsgChannel)
|
||||
bbbMsgBus.subscribe(analyticsActorRef, analyticsChannel)
|
||||
|
||||
outBus2.subscribe(learningDashboardActor, outBbbMsgMsgChannel)
|
||||
bbbMsgBus.subscribe(learningDashboardActor, analyticsChannel)
|
||||
|
||||
val bbbActor = system.actorOf(BigBlueButtonActor.props(system, eventBus, bbbMsgBus, outGW, healthzService), "bigbluebutton-actor")
|
||||
eventBus.subscribe(bbbActor, meetingManagerChannel)
|
||||
|
||||
val redisMessageHandlerActor = system.actorOf(ReceivedJsonMsgHandlerActor.props(bbbMsgBus, incomingJsonMessageBus))
|
||||
incomingJsonMessageBus.subscribe(redisMessageHandlerActor, toAkkaAppsJsonChannel)
|
||||
|
||||
val channelsToSubscribe = Seq(
|
||||
toAkkaAppsRedisChannel, fromVoiceConfRedisChannel, fromSfuRedisChannel,
|
||||
)
|
||||
|
||||
val redisSubscriberActor = system.actorOf(
|
||||
AppsRedisSubscriberActor.props(
|
||||
system,
|
||||
incomingJsonMessageBus,
|
||||
redisConfig,
|
||||
channelsToSubscribe,
|
||||
Nil,
|
||||
toAkkaAppsJsonChannel
|
||||
),
|
||||
"redis-subscriber"
|
||||
)
|
||||
|
||||
val bindingFuture = Http().bindAndHandle(apiService.routes, httpHost, httpPort)
|
||||
}
|
156
akka-bbb-apps/src/main/scala/org/bigbluebutton/LockSettingsUtil.scala
Executable file
156
akka-bbb-apps/src/main/scala/org/bigbluebutton/LockSettingsUtil.scala
Executable file
@ -0,0 +1,156 @@
|
||||
package org.bigbluebutton
|
||||
|
||||
import org.bigbluebutton.common2.msgs.{ BbbCommonEnvCoreMsg, BbbCoreEnvelope, BbbCoreHeaderWithMeetingId, MessageTypes, MuteUserInVoiceConfSysMsg, MuteUserInVoiceConfSysMsgBody, Routing }
|
||||
import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter }
|
||||
import org.bigbluebutton.core2.{ MeetingStatus2x }
|
||||
import org.bigbluebutton.core.apps.webcam.CameraHdlrHelpers
|
||||
import org.bigbluebutton.core.models.{
|
||||
Roles,
|
||||
Users2x,
|
||||
UserState,
|
||||
VoiceUserState,
|
||||
VoiceUsers,
|
||||
Webcams,
|
||||
WebcamStream
|
||||
}
|
||||
|
||||
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 applyMutingOfUsers(disableMic: Boolean, liveMeeting: LiveMeeting, outGW: OutMsgRouter): 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) {
|
||||
// Apply lock setting to users who are not listen only. (ralam dec 6, 2019)
|
||||
muteUserInVoiceConf(liveMeeting, outGW, vu, disableMic)
|
||||
}
|
||||
|
||||
// Make sure listen only users are muted. (ralam dec 6, 2019)
|
||||
if (vu.listenOnly && !vu.muted) {
|
||||
muteUserInVoiceConf(liveMeeting, outGW, vu, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def enforceLockSettingsForAllVoiceUsers(liveMeeting: LiveMeeting, outGW: OutMsgRouter): Unit = {
|
||||
val permissions = MeetingStatus2x.getPermissions(liveMeeting.status)
|
||||
applyMutingOfUsers(permissions.disableMic, liveMeeting, outGW)
|
||||
}
|
||||
|
||||
def enforceLockSettingsForVoiceUser(voiceUser: VoiceUserState, liveMeeting: LiveMeeting, outGW: OutMsgRouter): Unit = {
|
||||
val permissions = MeetingStatus2x.getPermissions(liveMeeting.status)
|
||||
if (permissions.disableMic) {
|
||||
Users2x.findWithIntId(liveMeeting.users2x, voiceUser.intId).foreach { user =>
|
||||
if (user.role == Roles.VIEWER_ROLE && user.locked) {
|
||||
// Make sure that user is muted when lock settings has mic disabled. (ralam dec 6, 2019
|
||||
if (!voiceUser.muted) {
|
||||
muteUserInVoiceConf(liveMeeting, outGW, voiceUser, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
enforceListenOnlyUserIsMuted(voiceUser.intId, liveMeeting, outGW)
|
||||
}
|
||||
}
|
||||
|
||||
private def enforceListenOnlyUserIsMuted(intUserId: String, liveMeeting: LiveMeeting, outGW: OutMsgRouter): Unit = {
|
||||
val voiceUser = VoiceUsers.findWithIntId(liveMeeting.voiceUsers, intUserId)
|
||||
voiceUser.foreach { vu =>
|
||||
// Make sure that listen only user is muted. (ralam dec 6, 2019
|
||||
if (vu.listenOnly && !vu.muted) {
|
||||
muteUserInVoiceConf(liveMeeting, outGW, vu, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def isMicrophoneSharingLocked(user: UserState, liveMeeting: LiveMeeting): Boolean = {
|
||||
val permissions = MeetingStatus2x.getPermissions(liveMeeting.status)
|
||||
|
||||
user.role == Roles.VIEWER_ROLE && user.locked && permissions.disableMic
|
||||
}
|
||||
|
||||
def isCameraBroadcastLocked(user: UserState, liveMeeting: LiveMeeting): Boolean = {
|
||||
val permissions = MeetingStatus2x.getPermissions(liveMeeting.status)
|
||||
|
||||
user.role == Roles.VIEWER_ROLE && user.locked && permissions.disableCam
|
||||
}
|
||||
|
||||
def isCameraSubscribeLocked(
|
||||
user: UserState, stream: WebcamStream, liveMeeting: LiveMeeting
|
||||
): Boolean = {
|
||||
var locked = false
|
||||
|
||||
for {
|
||||
publisher <- Users2x.findWithIntId(liveMeeting.users2x, stream.userId)
|
||||
} yield {
|
||||
if (MeetingStatus2x.webcamsOnlyForModeratorEnabled(liveMeeting.status)
|
||||
&& publisher.role != Roles.MODERATOR_ROLE
|
||||
&& user.role == Roles.VIEWER_ROLE
|
||||
&& user.locked) {
|
||||
locked = true
|
||||
}
|
||||
}
|
||||
|
||||
locked
|
||||
}
|
||||
|
||||
private def enforceSeeOtherViewersForUser(
|
||||
user: UserState, liveMeeting: LiveMeeting, outGW: OutMsgRouter
|
||||
): Unit = {
|
||||
if (MeetingStatus2x.webcamsOnlyForModeratorEnabled(liveMeeting.status)) {
|
||||
Webcams.findAll(liveMeeting.webcams) foreach { webcam =>
|
||||
|
||||
if (isCameraSubscribeLocked(user, webcam, liveMeeting)
|
||||
&& Webcams.isSubscriber(liveMeeting.webcams, user.intId, webcam.streamId)) {
|
||||
CameraHdlrHelpers.requestCamSubscriptionEjection(
|
||||
liveMeeting.props.meetingProp.intId,
|
||||
user.intId,
|
||||
webcam.streamId,
|
||||
outGW
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def enforceDisableCamForUser(
|
||||
user: UserState, liveMeeting: LiveMeeting, outGW: OutMsgRouter
|
||||
): Unit = {
|
||||
if (isCameraBroadcastLocked(user, liveMeeting)) {
|
||||
val broadcastedWebcams = Webcams.findWebcamsForUser(liveMeeting.webcams, user.intId)
|
||||
broadcastedWebcams foreach { webcam =>
|
||||
CameraHdlrHelpers.requestBroadcastedCamEjection(
|
||||
liveMeeting.props.meetingProp.intId,
|
||||
user.intId,
|
||||
webcam.streamId,
|
||||
outGW
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def enforceCamLockSettingsForUser(
|
||||
user: UserState, liveMeeting: LiveMeeting, outGW: OutMsgRouter
|
||||
): Unit = {
|
||||
enforceDisableCamForUser(user, liveMeeting, outGW)
|
||||
enforceSeeOtherViewersForUser(user, liveMeeting, outGW)
|
||||
}
|
||||
|
||||
def enforceCamLockSettingsForAllUsers(liveMeeting: LiveMeeting, outGW: OutMsgRouter): Unit = {
|
||||
Users2x.findLockedViewers(liveMeeting.users2x).foreach { user =>
|
||||
enforceCamLockSettingsForUser(user, liveMeeting, outGW)
|
||||
}
|
||||
}
|
||||
}
|
83
akka-bbb-apps/src/main/scala/org/bigbluebutton/SystemConfiguration.scala
Executable file
83
akka-bbb-apps/src/main/scala/org/bigbluebutton/SystemConfiguration.scala
Executable file
@ -0,0 +1,83 @@
|
||||
package org.bigbluebutton
|
||||
|
||||
import scala.util.Try
|
||||
import com.typesafe.config.ConfigFactory
|
||||
|
||||
trait SystemConfiguration {
|
||||
val config = ConfigFactory.load()
|
||||
|
||||
lazy val bbbWebHost = Try(config.getString("services.bbbWebHost")).getOrElse("localhost")
|
||||
lazy val bbbWebPort = Try(config.getInt("services.bbbWebPort")).getOrElse(8888)
|
||||
lazy val bbbWebAPI = Try(config.getString("services.bbbWebAPI")).getOrElse("localhost")
|
||||
lazy val bbbWebSharedSecret = Try(config.getString("services.sharedSecret")).getOrElse("changeme")
|
||||
lazy val bbbWebModeratorPassword = Try(config.getString("services.moderatorPassword")).getOrElse("changeme")
|
||||
lazy val bbbWebViewerPassword = Try(config.getString("services.viewerPassword")).getOrElse("changeme")
|
||||
lazy val keysExpiresInSec = Try(config.getInt("redis.keyExpiry")).getOrElse(14 * 86400) // 14 days
|
||||
|
||||
lazy val expireLastUserLeft = Try(config.getInt("expire.lastUserLeft")).getOrElse(60) // 1 minute
|
||||
lazy val expireNeverJoined = Try(config.getInt("expire.neverJoined")).getOrElse(5 * 60) // 5 minutes
|
||||
|
||||
lazy val analyticsChannel = Try(config.getString("eventBus.analyticsChannel")).getOrElse("analytics-channel")
|
||||
lazy val meetingManagerChannel = Try(config.getString("eventBus.meetingManagerChannel")).getOrElse("MeetingManagerChannel")
|
||||
lazy val outMessageChannel = Try(config.getString("eventBus.outMessageChannel")).getOrElse("OutgoingMessageChannel")
|
||||
lazy val incomingJsonMsgChannel = Try(config.getString("eventBus.incomingJsonMsgChannel")).getOrElse("IncomingJsonMsgChannel")
|
||||
lazy val outBbbMsgMsgChannel = Try(config.getString("eventBus.outBbbMsgMsgChannel")).getOrElse("OutBbbMsgChannel")
|
||||
lazy val recordServiceMessageChannel = Try(config.getString("eventBus.recordServiceMessageChannel")).getOrElse("RecordServiceMessageChannel")
|
||||
|
||||
lazy val toHTML5RedisChannel = Try(config.getString("redis.toHTML5RedisChannel")).getOrElse("to-html5-redis-channel")
|
||||
lazy val fromAkkaAppsChannel = Try(config.getString("eventBus.fromAkkaAppsChannel")).getOrElse("from-akka-apps-channel")
|
||||
lazy val toAkkaAppsChannel = Try(config.getString("eventBus.toAkkaAppsChannel")).getOrElse("to-akka-apps-channel")
|
||||
lazy val fromClientChannel = Try(config.getString("eventBus.fromClientChannel")).getOrElse("from-client-channel")
|
||||
lazy val toClientChannel = Try(config.getString("eventBus.toClientChannel")).getOrElse("to-client-channel")
|
||||
lazy val toAkkaAppsJsonChannel = Try(config.getString("eventBus.toAkkaAppsChannel")).getOrElse("to-akka-apps-json-channel")
|
||||
lazy val fromAkkaAppsJsonChannel = Try(config.getString("eventBus.fromAkkaAppsChannel")).getOrElse("from-akka-apps-json-channel")
|
||||
|
||||
lazy val applyPermissionCheck = Try(config.getBoolean("apps.checkPermissions")).getOrElse(false)
|
||||
lazy val ejectOnViolation = Try(config.getBoolean("apps.ejectOnViolation")).getOrElse(false)
|
||||
|
||||
lazy val voiceConfRecordPath = Try(config.getString("voiceConf.recordPath")).getOrElse("/var/freeswitch/meetings")
|
||||
lazy val voiceConfRecordCodec = Try(config.getString("voiceConf.recordCodec")).getOrElse("wav")
|
||||
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 recordingChapterBreakLengthInMinutes = Try(config.getInt("recording.chapterBreakLengthInMinutes")).getOrElse(0)
|
||||
|
||||
lazy val endMeetingWhenNoMoreAuthedUsers = Try(config.getBoolean("apps.endMeetingWhenNoMoreAuthedUsers")).getOrElse(false)
|
||||
lazy val endMeetingWhenNoMoreAuthedUsersAfterMinutes = Try(config.getInt("apps.endMeetingWhenNoMoreAuthedUsersAfterMinutes")).getOrElse(2)
|
||||
|
||||
lazy val transcriptWords = Try(config.getInt("transcript.words")).getOrElse(8)
|
||||
lazy val transcriptLines = Try(config.getInt("transcript.lines")).getOrElse(2)
|
||||
|
||||
lazy val reduceDuplicatedPick = Try(config.getBoolean("apps.reduceDuplicatedPick")).getOrElse(false)
|
||||
|
||||
// Redis server configuration
|
||||
lazy val redisHost = Try(config.getString("redis.host")).getOrElse("127.0.0.1")
|
||||
lazy val redisPort = Try(config.getInt("redis.port")).getOrElse(6379)
|
||||
lazy val redisPassword = Try(config.getString("redis.password")).getOrElse("")
|
||||
lazy val redisExpireKey = Try(config.getInt("redis.keyExpiry")).getOrElse(1209600)
|
||||
|
||||
// Redis channels
|
||||
lazy val toAkkaAppsRedisChannel = Try(config.getString("redis.toAkkaAppsRedisChannel")).getOrElse("to-akka-apps-redis-channel")
|
||||
lazy val fromAkkaAppsRedisChannel = Try(config.getString("redis.fromAkkaAppsRedisChannel")).getOrElse("from-akka-apps-redis-channel")
|
||||
|
||||
lazy val toVoiceConfRedisChannel = Try(config.getString("redis.toVoiceConfRedisChannel")).getOrElse("to-voice-conf-redis-channel")
|
||||
lazy val fromVoiceConfRedisChannel = Try(config.getString("redis.fromVoiceConfRedisChannel")).getOrElse("from-voice-conf-redis-channel")
|
||||
|
||||
lazy val toSfuRedisChannel = Try(config.getString("redis.toSfuRedisChannel")).getOrElse("to-sfu-redis-channel")
|
||||
lazy val fromSfuRedisChannel = Try(config.getString("redis.fromSfuRedisChannel")).getOrElse("from-sfu-redis-channel")
|
||||
|
||||
lazy val fromAkkaAppsWbRedisChannel = Try(config.getString("redis.fromAkkaAppsWbRedisChannel")).getOrElse("from-akka-apps-wb-redis-channel")
|
||||
lazy val fromAkkaAppsChatRedisChannel = Try(config.getString("redis.fromAkkaAppsChatRedisChannel")).getOrElse("from-akka-apps-chat-redis-channel")
|
||||
lazy val fromAkkaAppsPresRedisChannel = Try(config.getString("redis.fromAkkaAppsPresRedisChannel")).getOrElse("from-akka-apps-pres-redis-channel")
|
||||
|
||||
lazy val fromBbbWebRedisChannel = Try(config.getString("redis.fromBbbWebRedisChannel")).getOrElse("from-bbb-web-redis-channel")
|
||||
|
||||
lazy val analyticsIncludeChat = Try(config.getBoolean("analytics.includeChat")).getOrElse(true)
|
||||
|
||||
// Grab the "interface" parameter from the http config
|
||||
val httpHost = config.getString("http.interface")
|
||||
// Grab the "port" parameter from the http config
|
||||
val httpPort = config.getInt("http.port")
|
||||
}
|
189
akka-bbb-apps/src/main/scala/org/bigbluebutton/core/BigBlueButtonActor.scala
Executable file
189
akka-bbb-apps/src/main/scala/org/bigbluebutton/core/BigBlueButtonActor.scala
Executable file
@ -0,0 +1,189 @@
|
||||
package org.bigbluebutton.core
|
||||
|
||||
import java.io.{ PrintWriter, StringWriter }
|
||||
import akka.actor._
|
||||
import akka.actor.ActorLogging
|
||||
import akka.actor.SupervisorStrategy.Resume
|
||||
import akka.util.Timeout
|
||||
|
||||
import scala.concurrent.duration._
|
||||
import org.bigbluebutton.core.bus._
|
||||
import org.bigbluebutton.core.api._
|
||||
import org.bigbluebutton.SystemConfiguration
|
||||
|
||||
import java.util.concurrent.TimeUnit
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.running.RunningMeeting
|
||||
import org.bigbluebutton.core2.RunningMeetings
|
||||
import org.bigbluebutton.core2.message.senders.MsgBuilder
|
||||
import org.bigbluebutton.service.HealthzService
|
||||
|
||||
object BigBlueButtonActor extends SystemConfiguration {
|
||||
def props(
|
||||
system: ActorSystem,
|
||||
eventBus: InternalEventBus,
|
||||
bbbMsgBus: BbbMsgRouterEventBus,
|
||||
outGW: OutMessageGateway,
|
||||
healthzService: HealthzService
|
||||
): Props =
|
||||
Props(classOf[BigBlueButtonActor], system, eventBus, bbbMsgBus, outGW, healthzService)
|
||||
}
|
||||
|
||||
class BigBlueButtonActor(
|
||||
val system: ActorSystem,
|
||||
val eventBus: InternalEventBus, val bbbMsgBus: BbbMsgRouterEventBus,
|
||||
val outGW: OutMessageGateway,
|
||||
val healthzService: HealthzService
|
||||
) extends Actor
|
||||
with ActorLogging with SystemConfiguration {
|
||||
|
||||
implicit def executionContext = system.dispatcher
|
||||
implicit val timeout = Timeout(5 seconds)
|
||||
|
||||
private val meetings = new RunningMeetings
|
||||
|
||||
override val supervisorStrategy = OneForOneStrategy(maxNrOfRetries = 10, withinTimeRange = 1 minute) {
|
||||
case e: Exception => {
|
||||
val sw: StringWriter = new StringWriter()
|
||||
sw.write("An exception has been thrown on BigBlueButtonActor, exception message [" + e.getMessage() + "] (full stacktrace below)\n")
|
||||
e.printStackTrace(new PrintWriter(sw))
|
||||
log.error(sw.toString())
|
||||
Resume
|
||||
}
|
||||
}
|
||||
|
||||
override def preStart() {
|
||||
bbbMsgBus.subscribe(self, meetingManagerChannel)
|
||||
}
|
||||
|
||||
override def postStop() {
|
||||
bbbMsgBus.unsubscribe(self, meetingManagerChannel)
|
||||
}
|
||||
|
||||
def receive = {
|
||||
// Internal messages
|
||||
case msg: DestroyMeetingInternalMsg => handleDestroyMeeting(msg)
|
||||
|
||||
// 2x messages
|
||||
case msg: BbbCommonEnvCoreMsg => handleBbbCommonEnvCoreMsg(msg)
|
||||
case _ => // do nothing
|
||||
}
|
||||
|
||||
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 _ => log.warning("Cannot handle " + msg.envelope.name)
|
||||
}
|
||||
}
|
||||
|
||||
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")
|
||||
m.actorRef forward (msg)
|
||||
}
|
||||
}
|
||||
|
||||
def handleCreateMeetingReqMsg(msg: CreateMeetingReqMsg): Unit = {
|
||||
log.debug("RECEIVED CreateMeetingReqMsg msg {}", msg)
|
||||
|
||||
RunningMeetings.findWithId(meetings, msg.body.props.meetingProp.intId) match {
|
||||
case None =>
|
||||
log.info("Create meeting request. meetingId={}", msg.body.props.meetingProp.intId)
|
||||
|
||||
val m = RunningMeeting(msg.body.props, outGW, eventBus)
|
||||
|
||||
// Subscribe to meeting and voice events.
|
||||
eventBus.subscribe(m.actorRef, m.props.meetingProp.intId)
|
||||
eventBus.subscribe(m.actorRef, m.props.voiceProp.voiceConf)
|
||||
|
||||
bbbMsgBus.subscribe(m.actorRef, m.props.meetingProp.intId)
|
||||
bbbMsgBus.subscribe(m.actorRef, m.props.voiceProp.voiceConf)
|
||||
|
||||
RunningMeetings.add(meetings, m)
|
||||
|
||||
case Some(m) =>
|
||||
log.info("Meeting already created. meetingID={}", msg.body.props.meetingProp.intId)
|
||||
// do nothing
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
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).filter(_.props.systemProps.html5InstanceId == msg.body.html5InstanceId).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 handleDestroyMeeting(msg: DestroyMeetingInternalMsg): Unit = {
|
||||
|
||||
for {
|
||||
m <- RunningMeetings.findWithId(meetings, msg.meetingId)
|
||||
m2 <- RunningMeetings.remove(meetings, msg.meetingId)
|
||||
} yield {
|
||||
// Unsubscribe to meeting and voice events.
|
||||
eventBus.unsubscribe(m.actorRef, m.props.meetingProp.intId)
|
||||
eventBus.unsubscribe(m.actorRef, m.props.voiceProp.voiceConf)
|
||||
|
||||
bbbMsgBus.unsubscribe(m.actorRef, m.props.meetingProp.intId)
|
||||
bbbMsgBus.unsubscribe(m.actorRef, m.props.voiceProp.voiceConf)
|
||||
|
||||
// Delay sending DisconnectAllUsers to allow messages to reach the client
|
||||
// before the connections are closed.
|
||||
context.system.scheduler.scheduleOnce(Duration.create(2500, TimeUnit.MILLISECONDS)) {
|
||||
// Disconnect all clients
|
||||
|
||||
val disconnectEvnt = MsgBuilder.buildDisconnectAllClientsSysMsg(msg.meetingId, "meeting-destroyed")
|
||||
m2.outMsgRouter.send(disconnectEvnt)
|
||||
|
||||
log.info("Destroyed meetingId={}", msg.meetingId)
|
||||
val destroyedEvent = MsgBuilder.buildMeetingDestroyedEvtMsg(msg.meetingId)
|
||||
m2.outMsgRouter.send(destroyedEvent)
|
||||
|
||||
// Stop the meeting actor.
|
||||
context.stop(m.actorRef)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
9
akka-bbb-apps/src/main/scala/org/bigbluebutton/core/FooTypeBar.scala
Executable file
9
akka-bbb-apps/src/main/scala/org/bigbluebutton/core/FooTypeBar.scala
Executable file
@ -0,0 +1,9 @@
|
||||
package org.bigbluebutton.core {
|
||||
|
||||
object MessageType extends scala.Enumeration {
|
||||
type MessageType = Value
|
||||
val SYSTEM = Value("system")
|
||||
val BROADCAST = Value("broadcast")
|
||||
val DIRECT = Value("direct")
|
||||
}
|
||||
}
|
10
akka-bbb-apps/src/main/scala/org/bigbluebutton/core/OutMessageGateway.scala
Executable file
10
akka-bbb-apps/src/main/scala/org/bigbluebutton/core/OutMessageGateway.scala
Executable file
@ -0,0 +1,10 @@
|
||||
package org.bigbluebutton.core
|
||||
|
||||
import org.bigbluebutton.common2.msgs.{ BbbCommonEnvCoreMsg }
|
||||
|
||||
trait OutMessageGateway {
|
||||
|
||||
def send(msg: BbbCommonEnvCoreMsg): Unit
|
||||
|
||||
def record(msg: BbbCommonEnvCoreMsg): Unit
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package org.bigbluebutton.core
|
||||
|
||||
import org.bigbluebutton.SystemConfiguration
|
||||
import org.bigbluebutton.common2.msgs.BbbCommonEnvCoreMsg
|
||||
import org.bigbluebutton.core.bus._
|
||||
|
||||
object OutMessageGatewayImp {
|
||||
def apply(
|
||||
outBus2: OutEventBus2
|
||||
) =
|
||||
new OutMessageGatewayImp(outBus2)
|
||||
}
|
||||
|
||||
class OutMessageGatewayImp(
|
||||
outBus2: OutEventBus2
|
||||
) extends OutMessageGateway
|
||||
with SystemConfiguration {
|
||||
|
||||
def send(msg: BbbCommonEnvCoreMsg): Unit = {
|
||||
outBus2.publish(BbbOutMessage(outBbbMsgMsgChannel, msg))
|
||||
}
|
||||
|
||||
def record(msg: BbbCommonEnvCoreMsg): Unit = {
|
||||
outBus2.publish(BbbOutMessage(recordServiceMessageChannel, msg))
|
||||
}
|
||||
}
|
127
akka-bbb-apps/src/main/scala/org/bigbluebutton/core/api/InMessages.scala
Executable file
127
akka-bbb-apps/src/main/scala/org/bigbluebutton/core/api/InMessages.scala
Executable file
@ -0,0 +1,127 @@
|
||||
package org.bigbluebutton.core.api
|
||||
|
||||
import org.bigbluebutton.core.domain.{ BreakoutUser, BreakoutVoiceUser }
|
||||
import spray.json.JsObject
|
||||
case class InMessageHeader(name: String)
|
||||
case class InHeaderAndJsonPayload(header: InMessageHeader, payload: JsObject)
|
||||
case class MessageProcessException(message: String) extends Exception(message)
|
||||
|
||||
trait InMessage
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// System
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
case class IsMeetingActorAliveMessage(meetingId: String) extends InMessage
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// Internal Messages
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
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 ExtendMeetingDuration(meetingId: String, userId: String) extends InMessage
|
||||
case class DestroyMeetingInternalMsg(meetingId: String) extends InMessage
|
||||
|
||||
/**
|
||||
* Sent by breakout room to parent meeting the breakout had ended.
|
||||
* @param meetingId
|
||||
*/
|
||||
case class BreakoutRoomEndedInternalMsg(meetingId: String) extends InMessage
|
||||
|
||||
/**
|
||||
* Sent by breakout room to parent meeting that breakout room has been created.
|
||||
* @param parentId
|
||||
* @param breakoutId
|
||||
*/
|
||||
case class BreakoutRoomCreatedInternalMsg(parentId: String, breakoutId: String) extends InMessage
|
||||
|
||||
/**
|
||||
* Audit message to trigger breakout room to update parent meeting of list of users.
|
||||
* @param parentId
|
||||
* @param breakoutId
|
||||
*/
|
||||
case class SendBreakoutUsersAuditInternalMsg(parentId: String, breakoutId: String) extends InMessage
|
||||
|
||||
/**
|
||||
* Send by breakout room to parent meeting with list of users in breakout room.
|
||||
* @param parentId
|
||||
* @param breakoutId
|
||||
* @param users
|
||||
*/
|
||||
case class BreakoutRoomUsersUpdateInternalMsg(parentId: String, breakoutId: String,
|
||||
users: Vector[BreakoutUser],
|
||||
voiceUsers: Vector[BreakoutVoiceUser]) extends InMessage
|
||||
|
||||
/**
|
||||
* Sent by parent meeting to breakout room to end breakout room.
|
||||
* @param parentId
|
||||
* @param breakoutId
|
||||
*/
|
||||
case class EndBreakoutRoomInternalMsg(parentId: String, breakoutId: String, reason: String) extends InMessage
|
||||
|
||||
/**
|
||||
* Sent by parent meeting to breakout room to update time.
|
||||
* @param parentId
|
||||
* @param breakoutId
|
||||
* @param durationInSeconds
|
||||
*/
|
||||
case class UpdateBreakoutRoomTimeInternalMsg(parentId: String, breakoutId: String, durationInSeconds: Int) extends InMessage
|
||||
|
||||
/**
|
||||
* Sent by parent meeting to breakout room to extend time.
|
||||
* @param parentId
|
||||
* @param breakoutId
|
||||
* @param senderName
|
||||
* @param msg
|
||||
*/
|
||||
case class SendMessageToBreakoutRoomInternalMsg(parentId: String, breakoutId: String, senderName: String, msg: String) extends InMessage
|
||||
|
||||
/**
|
||||
* Sent by parent meeting to breakout room to eject user.
|
||||
* @param parentId
|
||||
* @param breakoutId
|
||||
* @param extUserId
|
||||
* @param ejectedBy
|
||||
* @param reason
|
||||
* @param reasonCode
|
||||
* @param ban
|
||||
*/
|
||||
case class EjectUserFromBreakoutInternalMsg(parentId: String, breakoutId: String, extUserId: String, ejectedBy: String, reason: String, reasonCode: String, ban: Boolean) extends InMessage
|
||||
|
||||
/**
|
||||
* Sent by parent meeting to breakout room to import annotated slides.
|
||||
* @param userId
|
||||
* @param parentMeetingId
|
||||
* @param allPages
|
||||
*/
|
||||
case class CapturePresentationReqInternalMsg(userId: String, parentMeetingId: String, allPages: Boolean = true) extends InMessage
|
||||
|
||||
/**
|
||||
* Sent by parent meeting to breakout room to import shared notes.
|
||||
* @param parentMeetingId
|
||||
* @param meetingName
|
||||
*/
|
||||
case class CaptureSharedNotesReqInternalMsg(parentMeetingId: String, meetingName: String) extends InMessage
|
||||
|
||||
// DeskShare
|
||||
case class DeskShareStartedRequest(conferenceName: String, callerId: String, callerIdName: String) extends InMessage
|
||||
case class DeskShareStoppedRequest(conferenceName: String, callerId: String, callerIdName: String) extends InMessage
|
||||
case class DeskShareRTMPBroadcastStartedRequest(conferenceName: String, streamname: String, videoWidth: Int, videoHeight: Int, timestamp: String) extends InMessage
|
||||
case class DeskShareRTMPBroadcastStoppedRequest(conferenceName: String, streamname: String, videoWidth: Int, videoHeight: Int, timestamp: String) extends InMessage
|
||||
case class DeskShareGetDeskShareInfoRequest(conferenceName: String, requesterID: String, replyTo: String) extends InMessage
|
@ -0,0 +1,14 @@
|
||||
package org.bigbluebutton.core.api
|
||||
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
object TimestampGenerator {
|
||||
|
||||
def generateTimestamp(): Long = {
|
||||
TimeUnit.NANOSECONDS.toMillis(System.nanoTime())
|
||||
}
|
||||
|
||||
def getCurrentTime(): Long = {
|
||||
System.currentTimeMillis();
|
||||
}
|
||||
}
|
87
akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/BreakoutModel.scala
Executable file
87
akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/BreakoutModel.scala
Executable file
@ -0,0 +1,87 @@
|
||||
package org.bigbluebutton.core.apps
|
||||
|
||||
import org.bigbluebutton.core.domain.{ BreakoutRoom2x, BreakoutUser }
|
||||
|
||||
object BreakoutModel {
|
||||
def create(
|
||||
parentId: String,
|
||||
id: String,
|
||||
externalId: String,
|
||||
name: String,
|
||||
sequence: Integer,
|
||||
shortName: String,
|
||||
isDefaultName: Boolean,
|
||||
freeJoin: Boolean,
|
||||
voiceConf: String,
|
||||
assignedUsers: Vector[String],
|
||||
captureNotes: Boolean,
|
||||
captureSlides: Boolean,
|
||||
): BreakoutRoom2x = {
|
||||
new BreakoutRoom2x(id, externalId, name, parentId, sequence, shortName, isDefaultName, freeJoin, voiceConf, assignedUsers, Vector(), Vector(), None, false, captureNotes, captureSlides)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
case class BreakoutModel(
|
||||
startedOn: Option[Long],
|
||||
durationInSeconds: Int,
|
||||
rooms: Map[String, BreakoutRoom2x]
|
||||
) {
|
||||
|
||||
def find(id: String): Option[BreakoutRoom2x] = {
|
||||
rooms.get(id)
|
||||
}
|
||||
|
||||
def findWithExternalId(externalId: String): Option[BreakoutRoom2x] = {
|
||||
rooms.values find (r => r.externalId == externalId)
|
||||
}
|
||||
|
||||
def getRooms(): Vector[BreakoutRoom2x] = {
|
||||
rooms.values.toVector
|
||||
}
|
||||
|
||||
def getNumberOfRooms(): Int = {
|
||||
rooms.size
|
||||
}
|
||||
|
||||
def started(room: BreakoutRoom2x, startedOnInMillis: Long): BreakoutRoom2x = {
|
||||
room.copy(started = true, startedOn = Some(startedOnInMillis))
|
||||
}
|
||||
|
||||
def hasAllStarted(): Boolean = {
|
||||
rooms.values.filter(r => r.started).size == rooms.values.size
|
||||
}
|
||||
|
||||
def update(room: BreakoutRoom2x): BreakoutModel = {
|
||||
copy(rooms = rooms + (room.id -> room))
|
||||
}
|
||||
|
||||
def getAssignedUsers(breakoutMeetingId: String): Option[Vector[String]] = {
|
||||
for {
|
||||
room <- rooms.get(breakoutMeetingId)
|
||||
} yield room.assignedUsers
|
||||
}
|
||||
|
||||
def updateBreakoutUsers(breakoutMeetingId: String, users: Vector[BreakoutUser]): BreakoutModel = {
|
||||
val model = for {
|
||||
room <- rooms.get(breakoutMeetingId)
|
||||
} yield {
|
||||
val newRoom = room.copy(users = users)
|
||||
copy(rooms = rooms + (newRoom.id -> newRoom))
|
||||
}
|
||||
|
||||
model match {
|
||||
case Some(m) => m
|
||||
case None => copy()
|
||||
}
|
||||
}
|
||||
|
||||
def removeRoom(id: String): BreakoutModel = {
|
||||
copy(rooms = rooms - id)
|
||||
}
|
||||
|
||||
def setTime(newDurationInSeconds: Int): BreakoutModel = {
|
||||
copy(durationInSeconds = newDurationInSeconds)
|
||||
}
|
||||
|
||||
}
|
123
akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/CaptionModel.scala
Executable file
123
akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/CaptionModel.scala
Executable file
@ -0,0 +1,123 @@
|
||||
package org.bigbluebutton.core.apps
|
||||
|
||||
import org.bigbluebutton.common2.msgs.TranscriptVO
|
||||
import scala.collection.immutable.HashMap
|
||||
|
||||
class CaptionModel {
|
||||
private var transcripts = new HashMap[String, TranscriptVO]()
|
||||
|
||||
private def createTranscript(name: String, locale: String, ownerId: String): TranscriptVO = {
|
||||
val transcript = TranscriptVO(ownerId, "", locale)
|
||||
transcripts += name -> transcript
|
||||
transcript
|
||||
}
|
||||
|
||||
private def findTranscriptByOwnerId(userId: String): Option[(String, TranscriptVO)] = {
|
||||
transcripts.find(_._2.ownerId == userId).foreach(t => {
|
||||
return Some(t)
|
||||
})
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
def editHistory(userId: String, startIndex: Integer, endIndex: Integer, name: String, text: String): Boolean = {
|
||||
var successfulEdit = false
|
||||
//println("editHistory entered")
|
||||
if (transcripts contains name) {
|
||||
val oldTranscript = transcripts(name)
|
||||
if (oldTranscript.ownerId == userId || userId == "system") {
|
||||
//println("editHistory found name:" + name)
|
||||
val oText: String = transcripts(name).text
|
||||
|
||||
if (startIndex >= 0 && endIndex <= oText.length && startIndex <= endIndex) {
|
||||
//println("editHistory passed index test")
|
||||
val sText: String = oText.substring(0, startIndex)
|
||||
val eText: String = oText.substring(endIndex)
|
||||
|
||||
transcripts += name -> transcripts(name).copy(text = (sText + text + eText))
|
||||
//println("editHistory new history is: " + transcripts(name).text)
|
||||
successfulEdit = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
successfulEdit
|
||||
}
|
||||
|
||||
def getTextTail(name: String): String = {
|
||||
var tail = ""
|
||||
if (transcripts contains name) {
|
||||
val text = transcripts(name).text
|
||||
if (text.size > 256) {
|
||||
tail = text.slice(text.size - 256, text.size)
|
||||
} else {
|
||||
tail = text
|
||||
}
|
||||
}
|
||||
|
||||
tail
|
||||
}
|
||||
|
||||
def getLocale(name: String): String = {
|
||||
var locale = ""
|
||||
if (transcripts contains name) {
|
||||
locale = transcripts(name).locale
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
if (transcripts.contains(name) && transcripts(name).ownerId == userId) {
|
||||
isOwner = true;
|
||||
}
|
||||
|
||||
isOwner
|
||||
}
|
||||
}
|
22
akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/ChatModel.scala
Executable file
22
akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/ChatModel.scala
Executable file
@ -0,0 +1,22 @@
|
||||
package org.bigbluebutton.core.apps
|
||||
|
||||
import org.bigbluebutton.common2.msgs.ChatMessageVO
|
||||
import scala.collection.mutable.ArrayBuffer
|
||||
|
||||
object ChatModel {
|
||||
def getChatHistory(chatModel: ChatModel): Array[ChatMessageVO] = {
|
||||
chatModel.messages.toArray
|
||||
}
|
||||
|
||||
def addNewChatMessage(chatModel: ChatModel, msg: ChatMessageVO) {
|
||||
chatModel.messages.append(msg)
|
||||
}
|
||||
|
||||
def clearPublicChatHistory(chatModel: ChatModel) {
|
||||
chatModel.messages.clear()
|
||||
}
|
||||
}
|
||||
|
||||
class ChatModel {
|
||||
private val messages = new ArrayBuffer[ChatMessageVO]()
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package org.bigbluebutton.core.apps
|
||||
|
||||
import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter }
|
||||
import org.bigbluebutton.core2.message.senders.{ MsgBuilder }
|
||||
|
||||
object ExternalVideoModel {
|
||||
def setURL(externalVideoModel: ExternalVideoModel, externalVideoUrl: String) {
|
||||
externalVideoModel.externalVideoUrl = externalVideoUrl
|
||||
}
|
||||
|
||||
def clear(externalVideoModel: ExternalVideoModel) {
|
||||
externalVideoModel.externalVideoUrl = ""
|
||||
}
|
||||
|
||||
def stop(outGW: OutMsgRouter, liveMeeting: LiveMeeting) {
|
||||
if (!liveMeeting.externalVideoModel.externalVideoUrl.isEmpty) {
|
||||
liveMeeting.externalVideoModel.externalVideoUrl = ""
|
||||
|
||||
val event = MsgBuilder.buildStopExternalVideoEvtMsg(liveMeeting.props.meetingProp.intId)
|
||||
outGW.send(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ExternalVideoModel {
|
||||
private var externalVideoUrl = ""
|
||||
}
|
17
akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/GuestsApp.scala
Executable file
17
akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/GuestsApp.scala
Executable file
@ -0,0 +1,17 @@
|
||||
package org.bigbluebutton.core.apps
|
||||
|
||||
import org.bigbluebutton.core.running.MeetingActor
|
||||
import org.bigbluebutton.core2.message.handlers.guests._
|
||||
|
||||
trait GuestsApp extends GetGuestsWaitingApprovalReqMsgHdlr
|
||||
with GuestsWaitingApprovedMsgHdlr
|
||||
with GuestWaitingLeftMsgHdlr
|
||||
with UpdatePositionInWaitingQueueReqMsgHdlr
|
||||
with SetGuestPolicyMsgHdlr
|
||||
with SetGuestLobbyMessageMsgHdlr
|
||||
with SetPrivateGuestLobbyMessageCmdMsgHdlr
|
||||
with GetGuestPolicyReqMsgHdlr {
|
||||
|
||||
this: MeetingActor =>
|
||||
|
||||
}
|
107
akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/PermissionCheck.scala
Executable file
107
akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/PermissionCheck.scala
Executable file
@ -0,0 +1,107 @@
|
||||
package org.bigbluebutton.core.apps
|
||||
|
||||
import org.bigbluebutton.SystemConfiguration
|
||||
import org.bigbluebutton.core.apps.users.UsersApp
|
||||
import org.bigbluebutton.core.models._
|
||||
import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter }
|
||||
import org.bigbluebutton.core2.message.senders.Sender
|
||||
|
||||
trait RightsManagementTrait extends SystemConfiguration {
|
||||
/**
|
||||
* This method will check if the user that issued the command has the correct permissions.
|
||||
*
|
||||
* Example of the permissions level are "AUTHENTICATED", "MODERATOR" and "GUEST". Example of roles
|
||||
* are "VIEWER" and "PRESENTER".
|
||||
*
|
||||
* @param permissionLevel Lowest permission needed to have access.
|
||||
* @param roleLevel Lowest role needed to have access.
|
||||
* @return true allows API to execute, false denies executing API
|
||||
*/
|
||||
def permissionFailed(permissionLevel: Int, roleLevel: Int, users: Users2x, userId: String): Boolean = {
|
||||
if (applyPermissionCheck) {
|
||||
!PermissionCheck.isAllowed(permissionLevel, roleLevel, users, userId)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
def filterPresentationMessage(users: Users2x, userId: String): Boolean = {
|
||||
// Check if the message are delayed presentation messages from the previous presenter
|
||||
// after a switch presenter has been made. ralam nov 22, 2017
|
||||
users.purgeOldPresenters()
|
||||
val now = System.currentTimeMillis()
|
||||
users.findOldPresenter(userId) match {
|
||||
case Some(op) => now - op.changedPresenterOn < 5000
|
||||
case None => false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object PermissionCheck extends SystemConfiguration {
|
||||
|
||||
val MOD_LEVEL = 100
|
||||
val AUTHED_LEVEL = 50
|
||||
val GUEST_LEVEL = 0
|
||||
|
||||
val PRESENTER_LEVEL = 100
|
||||
val VIEWER_LEVEL = 0
|
||||
|
||||
private def permissionToLevel(user: UserState): Int = {
|
||||
if (user.role == Roles.MODERATOR_ROLE) {
|
||||
MOD_LEVEL
|
||||
} else if (user.authed) {
|
||||
AUTHED_LEVEL
|
||||
} else {
|
||||
GUEST_LEVEL
|
||||
}
|
||||
}
|
||||
|
||||
private def roleToLevel(users: Users2x, user: UserState): Int = {
|
||||
if (Users2x.userIsInPresenterGroup(users, user.intId) || user.presenter) PRESENTER_LEVEL else VIEWER_LEVEL
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will check if the user that issued the command has the correct permissions.
|
||||
*
|
||||
* Example of the permissions level are "AUTHENTICATED", "MODERATOR" and "GUEST". Example of roles
|
||||
* are "VIEWER" and "PRESENTER".
|
||||
*
|
||||
* @param permissionLevel Lowest permission needed to have access.
|
||||
* @param roleLevel Lowest role needed to have access.
|
||||
* @return true allows API to execute, false denies executing API
|
||||
*/
|
||||
def isAllowed(permissionLevel: Int, roleLevel: Int, users: Users2x, userId: String): Boolean = {
|
||||
Users2x.findWithIntId(users, userId) match {
|
||||
case Some(user) =>
|
||||
val permLevelCheck = permissionToLevel(user) >= permissionLevel
|
||||
val roleLevelCheck = roleToLevel(users, user) >= roleLevel
|
||||
permLevelCheck && roleLevelCheck
|
||||
case None => false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
def ejectUserForFailedPermission(meetingId: String, userId: String, reason: String,
|
||||
outGW: OutMsgRouter, liveMeeting: LiveMeeting): Unit = {
|
||||
if (ejectOnViolation) {
|
||||
val ejectedBy = SystemUser.ID
|
||||
|
||||
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)
|
||||
} else {
|
||||
// TODO: get this object a context so it can use the akka logging system
|
||||
println(s"Skipping violation ejection of ${userId} trying to ${reason} in ${meetingId}")
|
||||
}
|
||||
}
|
||||
|
||||
def addOldPresenter(users: Users2x, userId: String): OldPresenter = {
|
||||
users.addOldPresenter(userId)
|
||||
}
|
||||
|
||||
def removeOldPresenter(users: Users2x, userId: String): Option[OldPresenter] = {
|
||||
users.removeOldPresenter(userId)
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
package org.bigbluebutton.core.apps
|
||||
|
||||
import org.bigbluebutton.common2.domain.PageVO
|
||||
|
||||
case class CurrentPresenter(userId: String, name: String, assignedBy: String)
|
||||
case class CurrentPresentationInfo(presenter: CurrentPresenter, presentations: Seq[Presentation])
|
||||
case class Presentation(id: String, name: String, current: Boolean = false,
|
||||
pages: scala.collection.immutable.Map[String, PageVO], downloadable: Boolean, removable: Boolean)
|
||||
|
||||
class PresentationModel {
|
||||
private var presentations = new scala.collection.immutable.HashMap[String, Presentation] // todo remove
|
||||
|
||||
def addPresentation(pres: Presentation) { // todo remove
|
||||
savePresentation(pres)
|
||||
}
|
||||
|
||||
def getPresentations(): Vector[Presentation] = { // todo remove
|
||||
presentations.values.toVector
|
||||
}
|
||||
|
||||
def getCurrentPresentation(): Option[Presentation] = { // todo remove
|
||||
presentations.values find (p => p.current)
|
||||
}
|
||||
|
||||
def getCurrentPage(pres: Presentation): Option[PageVO] = {
|
||||
pres.pages.values find (p => p.current)
|
||||
}
|
||||
|
||||
def getCurrentPage(): Option[PageVO] = {
|
||||
for {
|
||||
curPres <- getCurrentPresentation()
|
||||
curPage <- getCurrentPage(curPres)
|
||||
} yield curPage
|
||||
}
|
||||
|
||||
def setCurrentPresentation(presId: String): Option[Presentation] = { // todo remove
|
||||
getPresentations foreach (curPres => {
|
||||
if (curPres.id != presId) {
|
||||
val newPres = curPres.copy(current = false)
|
||||
savePresentation(newPres)
|
||||
}
|
||||
})
|
||||
|
||||
presentations.get(presId) match {
|
||||
case Some(pres) =>
|
||||
val cp = pres.copy(current = true)
|
||||
savePresentation(cp)
|
||||
Some(cp)
|
||||
case None => None
|
||||
}
|
||||
}
|
||||
|
||||
private def savePresentation(pres: Presentation) { // todo remove
|
||||
presentations += pres.id -> pres
|
||||
}
|
||||
|
||||
}
|
106
akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/ScreenshareModel.scala
Executable file
106
akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/ScreenshareModel.scala
Executable file
@ -0,0 +1,106 @@
|
||||
package org.bigbluebutton.core.apps
|
||||
|
||||
import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter }
|
||||
import org.bigbluebutton.core2.message.senders.MsgBuilder
|
||||
|
||||
object ScreenshareModel {
|
||||
def resetDesktopSharingParams(status: ScreenshareModel) = {
|
||||
status.broadcastingRTMP = false
|
||||
status.screenshareStarted = false
|
||||
status.rtmpBroadcastingUrl = ""
|
||||
status.screenshareVideoWidth = 0
|
||||
status.screenshareVideoHeight = 0
|
||||
status.voiceConf = ""
|
||||
status.screenshareConf = ""
|
||||
status.timestamp = ""
|
||||
status.hasAudio = false
|
||||
}
|
||||
|
||||
def getScreenshareStarted(status: ScreenshareModel): Boolean = {
|
||||
return status.screenshareStarted
|
||||
}
|
||||
|
||||
def setScreenshareStarted(status: ScreenshareModel, b: Boolean) {
|
||||
status.screenshareStarted = b
|
||||
}
|
||||
|
||||
def setScreenshareVideoWidth(status: ScreenshareModel, videoWidth: Int) {
|
||||
status.screenshareVideoWidth = videoWidth
|
||||
}
|
||||
|
||||
def setScreenshareVideoHeight(status: ScreenshareModel, videoHeight: Int) {
|
||||
status.screenshareVideoHeight = videoHeight
|
||||
}
|
||||
|
||||
def getScreenshareVideoWidth(status: ScreenshareModel): Int = {
|
||||
status.screenshareVideoWidth
|
||||
}
|
||||
|
||||
def getScreenshareVideoHeight(status: ScreenshareModel): Int = {
|
||||
status.screenshareVideoHeight
|
||||
}
|
||||
|
||||
def broadcastingRTMPStarted(status: ScreenshareModel) {
|
||||
status.broadcastingRTMP = true
|
||||
}
|
||||
|
||||
def isBroadcastingRTMP(status: ScreenshareModel): Boolean = {
|
||||
status.broadcastingRTMP
|
||||
}
|
||||
|
||||
def broadcastingRTMPStopped(status: ScreenshareModel) {
|
||||
status.broadcastingRTMP = false
|
||||
}
|
||||
|
||||
def setRTMPBroadcastingUrl(status: ScreenshareModel, path: String) {
|
||||
status.rtmpBroadcastingUrl = path
|
||||
}
|
||||
|
||||
def getRTMPBroadcastingUrl(status: ScreenshareModel): String = {
|
||||
status.rtmpBroadcastingUrl
|
||||
}
|
||||
|
||||
def setVoiceConf(status: ScreenshareModel, voiceConf: String): Unit = {
|
||||
status.voiceConf = voiceConf
|
||||
}
|
||||
|
||||
def getVoiceConf(status: ScreenshareModel): String = {
|
||||
status.voiceConf
|
||||
}
|
||||
|
||||
def setScreenshareConf(status: ScreenshareModel, screenshareConf: String): Unit = {
|
||||
status.screenshareConf = screenshareConf
|
||||
}
|
||||
|
||||
def getScreenshareConf(status: ScreenshareModel): String = {
|
||||
status.screenshareConf
|
||||
}
|
||||
|
||||
def setTimestamp(status: ScreenshareModel, timestamp: String): Unit = {
|
||||
status.timestamp = timestamp
|
||||
}
|
||||
|
||||
def getTimestamp(status: ScreenshareModel): String = {
|
||||
status.timestamp
|
||||
}
|
||||
|
||||
def setHasAudio(status: ScreenshareModel, hasAudio: Boolean): Unit = {
|
||||
status.hasAudio = hasAudio
|
||||
}
|
||||
|
||||
def getHasAudio(status: ScreenshareModel): Boolean = {
|
||||
status.hasAudio
|
||||
}
|
||||
}
|
||||
|
||||
class ScreenshareModel {
|
||||
private var rtmpBroadcastingUrl: String = ""
|
||||
private var screenshareStarted = false
|
||||
private var screenshareVideoWidth = 0
|
||||
private var screenshareVideoHeight = 0
|
||||
private var broadcastingRTMP = false
|
||||
private var voiceConf: String = ""
|
||||
private var screenshareConf: String = ""
|
||||
private var timestamp: String = ""
|
||||
private var hasAudio = false
|
||||
}
|
126
akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/WhiteboardModel.scala
Executable file
126
akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/WhiteboardModel.scala
Executable file
@ -0,0 +1,126 @@
|
||||
package org.bigbluebutton.core.apps
|
||||
|
||||
import scala.collection.immutable.HashMap
|
||||
import org.bigbluebutton.common2.msgs.AnnotationVO
|
||||
import org.bigbluebutton.core.apps.whiteboard.Whiteboard
|
||||
import org.bigbluebutton.SystemConfiguration
|
||||
|
||||
class WhiteboardModel extends SystemConfiguration {
|
||||
private var _whiteboards = new HashMap[String, Whiteboard]()
|
||||
|
||||
private def saveWhiteboard(wb: Whiteboard) {
|
||||
_whiteboards += wb.id -> wb
|
||||
}
|
||||
|
||||
def getWhiteboard(id: String): Whiteboard = {
|
||||
_whiteboards.get(id).getOrElse(createWhiteboard(id))
|
||||
}
|
||||
|
||||
def hasWhiteboard(id: String): Boolean = {
|
||||
_whiteboards.contains(id)
|
||||
}
|
||||
|
||||
private def createWhiteboard(wbId: String): Whiteboard = {
|
||||
Whiteboard(
|
||||
wbId,
|
||||
Array.empty[String],
|
||||
Array.empty[String],
|
||||
System.currentTimeMillis(),
|
||||
new HashMap[String, AnnotationVO]
|
||||
)
|
||||
}
|
||||
|
||||
private def deepMerge(test: Map[String, _], that: Map[String, _]): Map[String, _] =
|
||||
(for (k <- test.keys ++ that.keys) yield {
|
||||
val newValue =
|
||||
(test.get(k), that.get(k)) match {
|
||||
case (Some(v), None) => v
|
||||
case (None, Some(v)) => v
|
||||
case (Some(v1), Some(v2)) =>
|
||||
if (v1.isInstanceOf[Map[String, _]] && v2.isInstanceOf[Map[String, _]])
|
||||
deepMerge(v1.asInstanceOf[Map[String, _]], v2.asInstanceOf[Map[String, _]])
|
||||
else v2
|
||||
case (_, _) => ???
|
||||
}
|
||||
k -> newValue
|
||||
}).toMap
|
||||
|
||||
def addAnnotations(wbId: String, userId: String, annotations: Array[AnnotationVO], isPresenter: Boolean, isModerator: Boolean): Array[AnnotationVO] = {
|
||||
var annotationsAdded = Array[AnnotationVO]()
|
||||
val wb = getWhiteboard(wbId)
|
||||
var newAnnotationsMap = wb.annotationsMap
|
||||
for (annotation <- annotations) {
|
||||
val oldAnnotation = wb.annotationsMap.get(annotation.id)
|
||||
if (!oldAnnotation.isEmpty) {
|
||||
val hasPermission = isPresenter || isModerator || oldAnnotation.get.userId == userId
|
||||
if (hasPermission) {
|
||||
val newAnnotation = oldAnnotation.get.copy(annotationInfo = deepMerge(oldAnnotation.get.annotationInfo, annotation.annotationInfo))
|
||||
newAnnotationsMap += (annotation.id -> newAnnotation)
|
||||
annotationsAdded :+= annotation
|
||||
println(s"Updated annotation onpage [${wb.id}]. After numAnnotations=[${newAnnotationsMap.size}].")
|
||||
} else {
|
||||
println(s"User $userId doesn't have permission to edit annotation ${annotation.id}, ignoring...")
|
||||
}
|
||||
} else if (annotation.annotationInfo.contains("type")) {
|
||||
newAnnotationsMap += (annotation.id -> annotation)
|
||||
annotationsAdded :+= annotation
|
||||
println(s"Adding annotation to page [${wb.id}]. After numAnnotations=[${newAnnotationsMap.size}].")
|
||||
} else {
|
||||
println(s"New annotation [${annotation.id}] with no type, ignoring (probably received a remove message before and now the shape is incomplete, ignoring...")
|
||||
}
|
||||
}
|
||||
val newWb = wb.copy(annotationsMap = newAnnotationsMap)
|
||||
saveWhiteboard(newWb)
|
||||
annotationsAdded
|
||||
}
|
||||
|
||||
def getHistory(wbId: String): Array[AnnotationVO] = {
|
||||
val wb = getWhiteboard(wbId)
|
||||
wb.annotationsMap.values.toArray
|
||||
}
|
||||
|
||||
def deleteAnnotations(wbId: String, userId: String, annotationsIds: Array[String], isPresenter: Boolean, isModerator: Boolean): Array[String] = {
|
||||
var annotationsIdsRemoved = Array[String]()
|
||||
val wb = getWhiteboard(wbId)
|
||||
var newAnnotationsMap = wb.annotationsMap
|
||||
|
||||
for (annotationId <- annotationsIds) {
|
||||
val annotation = wb.annotationsMap.get(annotationId)
|
||||
|
||||
if (!annotation.isEmpty) {
|
||||
val hasPermission = isPresenter || isModerator || annotation.get.userId == userId
|
||||
if (hasPermission) {
|
||||
newAnnotationsMap -= annotationId
|
||||
println("Removing annotation on page [" + wb.id + "]. After numAnnotations=[" + newAnnotationsMap.size + "].")
|
||||
annotationsIdsRemoved :+= annotationId
|
||||
} else {
|
||||
println("User doesn't have permission to remove this annotation, ignoring...")
|
||||
}
|
||||
}
|
||||
}
|
||||
val newWb = wb.copy(annotationsMap = newAnnotationsMap)
|
||||
saveWhiteboard(newWb)
|
||||
annotationsIdsRemoved
|
||||
}
|
||||
|
||||
def modifyWhiteboardAccess(wbId: String, multiUser: Array[String]) {
|
||||
val wb = getWhiteboard(wbId)
|
||||
val newWb = wb.copy(multiUser = multiUser, oldMultiUser = wb.multiUser, changedModeOn = System.currentTimeMillis())
|
||||
saveWhiteboard(newWb)
|
||||
}
|
||||
|
||||
def getWhiteboardAccess(wbId: String): Array[String] = getWhiteboard(wbId).multiUser
|
||||
|
||||
def isNonEjectionGracePeriodOver(wbId: String, userId: String): Boolean = {
|
||||
val wb = getWhiteboard(wbId)
|
||||
val lastChange = System.currentTimeMillis() - wb.changedModeOn
|
||||
!(wb.oldMultiUser.contains(userId) && lastChange < 5000)
|
||||
}
|
||||
|
||||
def hasWhiteboardAccess(wbId: String, userId: String): Boolean = {
|
||||
val wb = getWhiteboard(wbId)
|
||||
wb.multiUser.contains(userId)
|
||||
}
|
||||
|
||||
def getChangedModeOn(wbId: String): Long = getWhiteboard(wbId).changedModeOn
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package org.bigbluebutton.core.apps.audiocaptions
|
||||
|
||||
import akka.actor.ActorContext
|
||||
|
||||
class AudioCaptionsApp2x(implicit val context: ActorContext)
|
||||
extends UpdateTranscriptPubMsgHdlr
|
||||
with AudioFloorChangedVoiceConfEvtMsgHdlr {
|
||||
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package org.bigbluebutton.core.apps.audiocaptions
|
||||
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.models.{ AudioCaptions, VoiceUsers }
|
||||
import org.bigbluebutton.core.running.LiveMeeting
|
||||
|
||||
trait AudioFloorChangedVoiceConfEvtMsgHdlr {
|
||||
this: AudioCaptionsApp2x =>
|
||||
|
||||
def handle(msg: AudioFloorChangedVoiceConfEvtMsg, liveMeeting: LiveMeeting): Unit = {
|
||||
for {
|
||||
vu <- VoiceUsers.findWithVoiceUserId(liveMeeting.voiceUsers, msg.body.voiceUserId)
|
||||
} yield AudioCaptions.setFloor(liveMeeting.audioCaptions, vu.intId)
|
||||
}
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
package org.bigbluebutton.core.apps.audiocaptions
|
||||
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.bus.MessageBus
|
||||
import org.bigbluebutton.core.models.AudioCaptions
|
||||
import org.bigbluebutton.core.running.LiveMeeting
|
||||
|
||||
trait UpdateTranscriptPubMsgHdlr {
|
||||
this: AudioCaptionsApp2x =>
|
||||
|
||||
def handle(msg: UpdateTranscriptPubMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
|
||||
def broadcastEvent(userId: String, transcriptId: String, transcript: String, locale: String): Unit = {
|
||||
val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, meetingId, "nodeJSapp")
|
||||
val envelope = BbbCoreEnvelope(TranscriptUpdatedEvtMsg.NAME, routing)
|
||||
val header = BbbClientMsgHeader(TranscriptUpdatedEvtMsg.NAME, meetingId, userId)
|
||||
val body = TranscriptUpdatedEvtMsgBody(transcriptId, transcript, locale)
|
||||
val event = TranscriptUpdatedEvtMsg(header, body)
|
||||
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
|
||||
|
||||
bus.outGW.send(msgEvent)
|
||||
}
|
||||
|
||||
// Adapt to the current captions' recording process
|
||||
def editTranscript(
|
||||
userId: String,
|
||||
start: Int,
|
||||
end: Int,
|
||||
locale: String,
|
||||
text: String
|
||||
): Unit = {
|
||||
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, meetingId, userId)
|
||||
val envelope = BbbCoreEnvelope(EditCaptionHistoryEvtMsg.NAME, routing)
|
||||
val header = BbbClientMsgHeader(EditCaptionHistoryEvtMsg.NAME, meetingId, userId)
|
||||
val body = EditCaptionHistoryEvtMsgBody(start, end, locale, locale, text)
|
||||
val event = EditCaptionHistoryEvtMsg(header, body)
|
||||
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
|
||||
|
||||
bus.outGW.send(msgEvent)
|
||||
}
|
||||
|
||||
if (AudioCaptions.isFloor(liveMeeting.audioCaptions, msg.header.userId)) {
|
||||
val (start, end, text) = AudioCaptions.editTranscript(
|
||||
liveMeeting.audioCaptions,
|
||||
msg.body.transcriptId,
|
||||
msg.body.start,
|
||||
msg.body.end,
|
||||
msg.body.text,
|
||||
msg.body.transcript,
|
||||
msg.body.locale
|
||||
)
|
||||
|
||||
editTranscript(
|
||||
msg.header.userId,
|
||||
start,
|
||||
end,
|
||||
msg.body.locale,
|
||||
text
|
||||
)
|
||||
|
||||
val transcript = AudioCaptions.parseTranscript(msg.body.transcript)
|
||||
|
||||
broadcastEvent(
|
||||
msg.header.userId,
|
||||
msg.body.transcriptId,
|
||||
transcript,
|
||||
msg.body.locale
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
package org.bigbluebutton.core.apps.breakout
|
||||
|
||||
import org.bigbluebutton.core.running.MeetingActor
|
||||
import java.net.URLEncoder
|
||||
import scala.collection.SortedSet
|
||||
import org.apache.commons.codec.digest.DigestUtils
|
||||
|
||||
trait BreakoutApp2x extends BreakoutRoomCreatedMsgHdlr
|
||||
with BreakoutRoomsListMsgHdlr
|
||||
with BreakoutRoomUsersUpdateMsgHdlr
|
||||
with CreateBreakoutRoomsCmdMsgHdlr
|
||||
with EndAllBreakoutRoomsMsgHdlr
|
||||
with UpdateBreakoutRoomsTimeMsgHdlr
|
||||
with ChangeUserBreakoutReqMsgHdlr
|
||||
with SendMessageToAllBreakoutRoomsMsgHdlr
|
||||
with SendMessageToBreakoutRoomInternalMsgHdlr
|
||||
with RequestBreakoutJoinURLReqMsgHdlr
|
||||
with SendBreakoutUsersUpdateMsgHdlr
|
||||
with TransferUserToMeetingRequestHdlr
|
||||
with EndBreakoutRoomInternalMsgHdlr
|
||||
with UpdateBreakoutRoomTimeInternalMsgHdlr
|
||||
with EjectUserFromBreakoutInternalMsgHdlr
|
||||
with BreakoutRoomEndedInternalMsgHdlr {
|
||||
|
||||
this: MeetingActor =>
|
||||
|
||||
}
|
||||
|
||||
object BreakoutRoomsUtil {
|
||||
def createMeetingIds(id: String, index: Int): (String, String) = {
|
||||
val timeStamp = System.currentTimeMillis()
|
||||
val externalHash = DigestUtils.sha1Hex(id.concat("-").concat(timeStamp.toString()).concat("-").concat(index.toString()))
|
||||
val externalId = externalHash.concat("-").concat(timeStamp.toString())
|
||||
val internalId = DigestUtils.sha1Hex(externalId).concat("-").concat(timeStamp.toString())
|
||||
(internalId, externalId)
|
||||
}
|
||||
|
||||
def createVoiceConfId(id: String, index: Int): String = {
|
||||
id.concat(index.toString())
|
||||
}
|
||||
|
||||
def createJoinURL(webAPI: String, apiCall: String, baseString: String, checksum: String): String = {
|
||||
val apiURL = if (webAPI.endsWith("/")) webAPI else webAPI.concat("/")
|
||||
apiURL.concat(apiCall).concat("?").concat(baseString).concat("&checksum=").concat(checksum)
|
||||
}
|
||||
|
||||
//
|
||||
//checksum() -- Return a checksum based on SHA-1 digest
|
||||
//
|
||||
def checksum(s: String): String = {
|
||||
DigestUtils.sha256Hex(s);
|
||||
}
|
||||
|
||||
def calculateChecksum(apiCall: String, baseString: String, sharedSecret: String): String = {
|
||||
checksum(apiCall.concat(baseString).concat(sharedSecret))
|
||||
}
|
||||
|
||||
def joinParams(username: String, userId: String, isBreakout: Boolean, breakoutMeetingId: String,
|
||||
password: String): (collection.immutable.Map[String, String], collection.immutable.Map[String, String]) = {
|
||||
val params = collection.immutable.HashMap(
|
||||
"fullName" -> urlEncode(username),
|
||||
"userID" -> urlEncode(userId),
|
||||
"isBreakout" -> urlEncode(isBreakout.toString()),
|
||||
"meetingID" -> urlEncode(breakoutMeetingId),
|
||||
"password" -> urlEncode(password),
|
||||
"redirect" -> urlEncode("true")
|
||||
)
|
||||
|
||||
(params, params + ("joinViaHtml5" -> urlEncode("true")))
|
||||
}
|
||||
|
||||
def sortParams(params: collection.immutable.Map[String, String]): SortedSet[String] = {
|
||||
collection.immutable.SortedSet[String]() ++ params.keySet
|
||||
}
|
||||
|
||||
//From the list of parameters we want to pass. Creates a base string with parameters
|
||||
//sorted in alphabetical order for us to sign.
|
||||
def createBaseString(params: collection.immutable.Map[String, String]): String = {
|
||||
val csbuf = new StringBuffer()
|
||||
val keys = sortParams(params)
|
||||
|
||||
var first = true;
|
||||
for (key <- keys) {
|
||||
for (value <- params.get(key)) {
|
||||
if (first) {
|
||||
first = false;
|
||||
} else {
|
||||
csbuf.append("&");
|
||||
}
|
||||
|
||||
csbuf.append(key);
|
||||
csbuf.append("=");
|
||||
csbuf.append(value);
|
||||
}
|
||||
}
|
||||
|
||||
return csbuf.toString();
|
||||
}
|
||||
|
||||
def urlEncode(s: String): String = {
|
||||
URLEncoder.encode(s, "UTF-8");
|
||||
}
|
||||
}
|
@ -0,0 +1,125 @@
|
||||
package org.bigbluebutton.core.apps.breakout
|
||||
|
||||
import org.bigbluebutton.SystemConfiguration
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.api.{ BreakoutRoomUsersUpdateInternalMsg }
|
||||
import org.bigbluebutton.core.bus.{ BigBlueButtonEvent, InternalEventBus }
|
||||
import org.bigbluebutton.core.domain.{ BreakoutUser, BreakoutVoiceUser }
|
||||
import org.bigbluebutton.core.models.{ Users2x, VoiceUsers }
|
||||
import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter }
|
||||
|
||||
object BreakoutHdlrHelpers extends SystemConfiguration {
|
||||
def sendJoinURL(
|
||||
liveMeeting: LiveMeeting,
|
||||
outGW: OutMsgRouter,
|
||||
userId: String,
|
||||
externalMeetingId: String,
|
||||
roomSequence: String,
|
||||
breakoutId: String
|
||||
) {
|
||||
for {
|
||||
(redirectToHtml5JoinURL, redirectJoinURL) <- getRedirectUrls(liveMeeting, userId, externalMeetingId, roomSequence)
|
||||
} yield {
|
||||
sendJoinURLMsg(
|
||||
outGW,
|
||||
liveMeeting.props.meetingProp.intId,
|
||||
breakoutId,
|
||||
externalMeetingId,
|
||||
userId,
|
||||
redirectJoinURL,
|
||||
redirectToHtml5JoinURL
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
def getRedirectUrls(
|
||||
liveMeeting: LiveMeeting,
|
||||
userId: String,
|
||||
externalMeetingId: String,
|
||||
roomSequence: String
|
||||
): Option[(String, String)] = {
|
||||
for {
|
||||
user <- Users2x.findWithIntId(liveMeeting.users2x, userId)
|
||||
apiCall = "join"
|
||||
(redirectParams, redirectToHtml5Params) = BreakoutRoomsUtil.joinParams(user.name, userId + "-" + roomSequence, true,
|
||||
externalMeetingId, liveMeeting.props.password.moderatorPass)
|
||||
// We generate a first url with redirect -> true
|
||||
redirectBaseString = BreakoutRoomsUtil.createBaseString(redirectParams)
|
||||
redirectJoinURL = BreakoutRoomsUtil.createJoinURL(bbbWebAPI, apiCall, redirectBaseString,
|
||||
BreakoutRoomsUtil.calculateChecksum(apiCall, redirectBaseString, bbbWebSharedSecret))
|
||||
// We generate a second url with redirect -> true and joinViaHtml5 -> true
|
||||
redirectToHtml5BaseString = BreakoutRoomsUtil.createBaseString(redirectToHtml5Params)
|
||||
redirectToHtml5JoinURL = BreakoutRoomsUtil.createJoinURL(bbbWebAPI, apiCall, redirectToHtml5BaseString,
|
||||
BreakoutRoomsUtil.calculateChecksum(apiCall, redirectToHtml5BaseString, bbbWebSharedSecret))
|
||||
} yield {
|
||||
(redirectToHtml5JoinURL, redirectJoinURL)
|
||||
}
|
||||
}
|
||||
|
||||
def sendJoinURLMsg(
|
||||
outGW: OutMsgRouter,
|
||||
meetingId: String,
|
||||
breakoutId: String,
|
||||
externalId: String,
|
||||
userId: String,
|
||||
redirectJoinURL: String,
|
||||
redirectToHtml5JoinURL: String
|
||||
): Unit = {
|
||||
def build(meetingId: String, breakoutId: String,
|
||||
userId: String, redirectJoinURL: String, redirectToHtml5JoinURL: String): BbbCommonEnvCoreMsg = {
|
||||
val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, meetingId, userId)
|
||||
val envelope = BbbCoreEnvelope(BreakoutRoomJoinURLEvtMsg.NAME, routing)
|
||||
val header = BbbClientMsgHeader(BreakoutRoomJoinURLEvtMsg.NAME, meetingId, userId)
|
||||
|
||||
val body = BreakoutRoomJoinURLEvtMsgBody(meetingId, breakoutId, externalId,
|
||||
userId, redirectJoinURL, redirectToHtml5JoinURL)
|
||||
val event = BreakoutRoomJoinURLEvtMsg(header, body)
|
||||
BbbCommonEnvCoreMsg(envelope, event)
|
||||
}
|
||||
|
||||
val msgEvent = build(meetingId, breakoutId, userId, redirectJoinURL, redirectToHtml5JoinURL)
|
||||
outGW.send(msgEvent)
|
||||
|
||||
}
|
||||
|
||||
def sendChangeUserBreakoutMsg(
|
||||
outGW: OutMsgRouter,
|
||||
meetingId: String,
|
||||
userId: String,
|
||||
fromBreakoutId: String,
|
||||
toBreakoutId: String,
|
||||
redirectToHtml5JoinURL: String
|
||||
): Unit = {
|
||||
def build(meetingId: String, userId: String, fromBreakoutId: String, toBreakoutId: String, redirectToHtml5JoinURL: String): BbbCommonEnvCoreMsg = {
|
||||
val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, meetingId, userId)
|
||||
val envelope = BbbCoreEnvelope(ChangeUserBreakoutEvtMsg.NAME, routing)
|
||||
val header = BbbClientMsgHeader(ChangeUserBreakoutEvtMsg.NAME, meetingId, userId)
|
||||
|
||||
val body = ChangeUserBreakoutEvtMsgBody(meetingId, userId, fromBreakoutId, toBreakoutId, redirectToHtml5JoinURL)
|
||||
val event = ChangeUserBreakoutEvtMsg(header, body)
|
||||
BbbCommonEnvCoreMsg(envelope, event)
|
||||
}
|
||||
|
||||
val msgEvent = build(meetingId, userId, fromBreakoutId, toBreakoutId, redirectToHtml5JoinURL)
|
||||
outGW.send(msgEvent)
|
||||
|
||||
}
|
||||
|
||||
def updateParentMeetingWithUsers(
|
||||
liveMeeting: LiveMeeting,
|
||||
eventBus: InternalEventBus
|
||||
): Unit = {
|
||||
|
||||
val users = Users2x.findAll(liveMeeting.users2x)
|
||||
val breakoutUsers = users map { u => new BreakoutUser(u.extId, u.name) }
|
||||
|
||||
val voiceUsers = VoiceUsers.findAll(liveMeeting.voiceUsers)
|
||||
val breakoutVoiceUsers = voiceUsers map { vu => BreakoutVoiceUser(vu.intId, vu.intId, vu.voiceUserId) }
|
||||
|
||||
eventBus.publish(BigBlueButtonEvent(
|
||||
liveMeeting.props.breakoutProps.parentId,
|
||||
new BreakoutRoomUsersUpdateInternalMsg(liveMeeting.props.breakoutProps.parentId, liveMeeting.props.meetingProp.intId,
|
||||
breakoutUsers, breakoutVoiceUsers)
|
||||
))
|
||||
}
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
package org.bigbluebutton.core.apps.breakout
|
||||
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.api.BreakoutRoomCreatedInternalMsg
|
||||
import org.bigbluebutton.core.apps.BreakoutModel
|
||||
import org.bigbluebutton.core.domain.{ BreakoutRoom2x, MeetingState2x }
|
||||
import org.bigbluebutton.core.running.{ LiveMeeting, MeetingActor, OutMsgRouter }
|
||||
|
||||
trait BreakoutRoomCreatedMsgHdlr {
|
||||
this: MeetingActor =>
|
||||
|
||||
val liveMeeting: LiveMeeting
|
||||
val outGW: OutMsgRouter
|
||||
|
||||
def handleBreakoutRoomCreatedInternalMsg(msg: BreakoutRoomCreatedInternalMsg, state: MeetingState2x): MeetingState2x = {
|
||||
|
||||
val updatedModel = for {
|
||||
breakoutModel <- state.breakout
|
||||
room <- breakoutModel.find(msg.breakoutId)
|
||||
startedRoom = breakoutModel.started(room, System.currentTimeMillis())
|
||||
} yield {
|
||||
val updatedRoom = sendBreakoutRoomStarted(startedRoom)
|
||||
var updatedModel = breakoutModel.update(updatedRoom)
|
||||
// We postpone sending invitation until all breakout rooms have been created
|
||||
if (updatedModel.hasAllStarted()) {
|
||||
updatedModel = updatedModel.copy(startedOn = Some(System.currentTimeMillis()))
|
||||
updatedModel = sendBreakoutRoomsList(updatedModel)
|
||||
}
|
||||
updatedModel
|
||||
}
|
||||
|
||||
updatedModel match {
|
||||
case Some(model) => state.update(Some(model))
|
||||
case None => state
|
||||
}
|
||||
}
|
||||
|
||||
def buildBreakoutRoomsListEvtMsg(meetingId: String, rooms: Vector[BreakoutRoomInfo], roomsReady: Boolean): BbbCommonEnvCoreMsg = {
|
||||
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, meetingId, "not-used")
|
||||
val envelope = BbbCoreEnvelope(BreakoutRoomsListEvtMsg.NAME, routing)
|
||||
val header = BbbClientMsgHeader(BreakoutRoomsListEvtMsg.NAME, meetingId, "not-used")
|
||||
|
||||
val body = BreakoutRoomsListEvtMsgBody(meetingId, rooms, roomsReady)
|
||||
val event = BreakoutRoomsListEvtMsg(header, body)
|
||||
BbbCommonEnvCoreMsg(envelope, event)
|
||||
}
|
||||
|
||||
def sendBreakoutRoomsList(breakoutModel: BreakoutModel): BreakoutModel = {
|
||||
val breakoutRooms = breakoutModel.rooms.values.toVector map { r =>
|
||||
val html5JoinUrls = for {
|
||||
user <- r.assignedUsers
|
||||
(redirectToHtml5JoinURL, redirectJoinURL) <- BreakoutHdlrHelpers.getRedirectUrls(liveMeeting, user, r.externalId, r.sequence.toString())
|
||||
} yield (user -> redirectToHtml5JoinURL)
|
||||
|
||||
new BreakoutRoomInfo(r.name, r.externalId, r.id, r.sequence, r.shortName, r.isDefaultName, r.freeJoin, html5JoinUrls.toMap, r.captureNotes, r.captureSlides)
|
||||
}
|
||||
|
||||
log.info("Sending breakout rooms list to {} with containing {} room(s)", liveMeeting.props.meetingProp.intId, breakoutRooms.length)
|
||||
|
||||
val msgEvent = buildBreakoutRoomsListEvtMsg(liveMeeting.props.meetingProp.intId, breakoutRooms, true)
|
||||
outGW.send(msgEvent)
|
||||
|
||||
breakoutModel
|
||||
}
|
||||
|
||||
def sendBreakoutRoomStarted(room: BreakoutRoom2x): BreakoutRoom2x = {
|
||||
log.info("Sending breakout room started {} for parent meeting {} ", room.id, room.parentId)
|
||||
|
||||
def build(meetingId: String, breakout: BreakoutRoomInfo): BbbCommonEnvCoreMsg = {
|
||||
val routing = Routing.addMsgToClientRouting(
|
||||
MessageTypes.BROADCAST_TO_MEETING,
|
||||
liveMeeting.props.meetingProp.intId, "not-used"
|
||||
)
|
||||
val envelope = BbbCoreEnvelope(BreakoutRoomStartedEvtMsg.NAME, routing)
|
||||
val header = BbbClientMsgHeader(BreakoutRoomStartedEvtMsg.NAME, liveMeeting.props.meetingProp.intId, "not-used")
|
||||
|
||||
val body = BreakoutRoomStartedEvtMsgBody(meetingId, breakout)
|
||||
val event = BreakoutRoomStartedEvtMsg(header, body)
|
||||
BbbCommonEnvCoreMsg(envelope, event)
|
||||
}
|
||||
|
||||
val breakoutInfo = BreakoutRoomInfo(room.name, room.externalId, room.id, room.sequence, room.shortName, room.isDefaultName, room.freeJoin, Map(), room.captureNotes, room.captureSlides)
|
||||
val event = build(liveMeeting.props.meetingProp.intId, breakoutInfo)
|
||||
outGW.send(event)
|
||||
|
||||
room
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
package org.bigbluebutton.core.apps.breakout
|
||||
|
||||
import org.bigbluebutton.core.api.BreakoutRoomEndedInternalMsg
|
||||
import org.bigbluebutton.core.domain.MeetingState2x
|
||||
import org.bigbluebutton.core.running.{ MeetingActor, OutMsgRouter }
|
||||
import org.bigbluebutton.core2.message.senders.MsgBuilder
|
||||
|
||||
trait BreakoutRoomEndedInternalMsgHdlr {
|
||||
this: MeetingActor =>
|
||||
|
||||
val outGW: OutMsgRouter
|
||||
|
||||
def handleBreakoutRoomEndedInternalMsg(msg: BreakoutRoomEndedInternalMsg, state: MeetingState2x): MeetingState2x = {
|
||||
log.info("Received breakout room ended. breakoutId={}", msg.meetingId)
|
||||
|
||||
// send out BreakoutRoomEndedEvtMsg to inform clients the breakout has ended
|
||||
outGW.send(MsgBuilder.buildBreakoutRoomEndedEvtMsg(liveMeeting.props.meetingProp.intId, "not-used",
|
||||
msg.meetingId))
|
||||
|
||||
val updatedModel = for {
|
||||
breakoutModel <- state.breakout
|
||||
} yield {
|
||||
breakoutModel.removeRoom(msg.meetingId)
|
||||
}
|
||||
|
||||
updatedModel match {
|
||||
case Some(model) =>
|
||||
if (model.rooms.isEmpty) {
|
||||
// All breakout rooms have ended
|
||||
val notifyEvent = MsgBuilder.buildNotifyAllInMeetingEvtMsg(
|
||||
liveMeeting.props.meetingProp.intId,
|
||||
"info",
|
||||
"rooms",
|
||||
"app.toast.breakoutRoomEnded",
|
||||
"Message when the breakout room is ended",
|
||||
Vector()
|
||||
)
|
||||
outGW.send(notifyEvent)
|
||||
|
||||
state.update(None)
|
||||
} else {
|
||||
state.update(Some(model))
|
||||
}
|
||||
case None =>
|
||||
state
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,60 @@
|
||||
package org.bigbluebutton.core.apps.breakout
|
||||
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.api.BreakoutRoomUsersUpdateInternalMsg
|
||||
import org.bigbluebutton.core.domain.{ BreakoutRoom2x, MeetingState2x }
|
||||
import org.bigbluebutton.core.models.{ RegisteredUsers, Users2x }
|
||||
import org.bigbluebutton.core.running.{ MeetingActor, OutMsgRouter }
|
||||
|
||||
trait BreakoutRoomUsersUpdateMsgHdlr {
|
||||
this: MeetingActor =>
|
||||
|
||||
val outGW: OutMsgRouter
|
||||
|
||||
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 {
|
||||
breakoutRoomUser <- updatedRoom.users
|
||||
user <- Users2x.findWithBreakoutRoomId(liveMeeting.users2x, breakoutRoomUser.id)
|
||||
} yield Users2x.updateLastUserActivity(liveMeeting.users2x, user)
|
||||
|
||||
//Update lastBreakout in registeredUsers to avoid lose this info when the user leaves
|
||||
for {
|
||||
breakoutRoomUser <- updatedRoom.users
|
||||
u <- RegisteredUsers.findWithBreakoutRoomId(breakoutRoomUser.id, liveMeeting.registeredUsers)
|
||||
} yield {
|
||||
if (room != null && (u.lastBreakoutRoom == null || u.lastBreakoutRoom.id != room.id)) {
|
||||
RegisteredUsers.updateUserLastBreakoutRoom(liveMeeting.registeredUsers, u, room)
|
||||
}
|
||||
}
|
||||
|
||||
model.update(updatedRoom)
|
||||
}
|
||||
|
||||
breakoutModel match {
|
||||
case Some(model) => state.update(Some(model))
|
||||
case None => state
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package org.bigbluebutton.core.apps.breakout
|
||||
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.domain.MeetingState2x
|
||||
import org.bigbluebutton.core.running.{ MeetingActor, OutMsgRouter }
|
||||
|
||||
trait BreakoutRoomsListMsgHdlr {
|
||||
this: MeetingActor =>
|
||||
|
||||
val outGW: OutMsgRouter
|
||||
|
||||
def handleBreakoutRoomsListMsg(msg: BreakoutRoomsListMsg, state: MeetingState2x): MeetingState2x = {
|
||||
|
||||
def broadcastEvent(rooms: Vector[BreakoutRoomInfo], roomsReady: Boolean): Unit = {
|
||||
log.info("Sending breakout rooms list to {} with containing {} room(s)", props.meetingProp.intId, rooms.length)
|
||||
|
||||
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, props.meetingProp.intId, msg.header.userId)
|
||||
val envelope = BbbCoreEnvelope(BreakoutRoomsListEvtMsg.NAME, routing)
|
||||
val header = BbbClientMsgHeader(BreakoutRoomsListEvtMsg.NAME, props.meetingProp.intId, msg.header.userId)
|
||||
|
||||
val body = BreakoutRoomsListEvtMsgBody(msg.body.meetingId, rooms, roomsReady)
|
||||
val event = BreakoutRoomsListEvtMsg(header, body)
|
||||
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
|
||||
outGW.send(msgEvent)
|
||||
}
|
||||
|
||||
for {
|
||||
breakoutModel <- state.breakout
|
||||
} yield {
|
||||
val rooms = breakoutModel.rooms.values.toVector map { r =>
|
||||
new BreakoutRoomInfo(r.name, r.externalId, r.id, r.sequence, r.shortName, r.isDefaultName, r.freeJoin, Map(), r.captureNotes, r.captureSlides)
|
||||
}
|
||||
val ready = breakoutModel.hasAllStarted()
|
||||
broadcastEvent(rooms, ready)
|
||||
}
|
||||
|
||||
state
|
||||
}
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
package org.bigbluebutton.core.apps.breakout
|
||||
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.api.EjectUserFromBreakoutInternalMsg
|
||||
import org.bigbluebutton.core.apps.breakout.BreakoutHdlrHelpers.{ getRedirectUrls }
|
||||
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
|
||||
import org.bigbluebutton.core.bus.BigBlueButtonEvent
|
||||
import org.bigbluebutton.core.domain.MeetingState2x
|
||||
import org.bigbluebutton.core.models.{ EjectReasonCode }
|
||||
import org.bigbluebutton.core.running.{ MeetingActor, OutMsgRouter }
|
||||
import org.bigbluebutton.core2.message.senders.{ MsgBuilder }
|
||||
|
||||
trait ChangeUserBreakoutReqMsgHdlr extends RightsManagementTrait {
|
||||
this: MeetingActor =>
|
||||
|
||||
val outGW: OutMsgRouter
|
||||
|
||||
def handleChangeUserBreakoutReqMsg(msg: ChangeUserBreakoutReqMsg, state: MeetingState2x): MeetingState2x = {
|
||||
|
||||
if (permissionFailed(PermissionCheck.MOD_LEVEL, PermissionCheck.VIEWER_LEVEL, liveMeeting.users2x, msg.header.userId)) {
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
val reason = "No permission to move user among breakout rooms."
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, outGW, liveMeeting)
|
||||
state
|
||||
} else {
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
|
||||
for {
|
||||
breakoutModel <- state.breakout
|
||||
} yield {
|
||||
|
||||
//Eject user from room From
|
||||
for {
|
||||
roomFrom <- breakoutModel.rooms.get(msg.body.fromBreakoutId)
|
||||
} yield {
|
||||
roomFrom.users.filter(u => u.id == msg.body.userId + "-" + roomFrom.sequence).foreach(user => {
|
||||
eventBus.publish(BigBlueButtonEvent(roomFrom.id, EjectUserFromBreakoutInternalMsg(meetingId, roomFrom.id, user.id, msg.header.userId, "User moved to another room", EjectReasonCode.EJECT_USER, false)))
|
||||
})
|
||||
}
|
||||
|
||||
//Get join URL for room To
|
||||
val redirectToHtml5JoinURL = (
|
||||
for {
|
||||
roomTo <- breakoutModel.rooms.get(msg.body.toBreakoutId)
|
||||
(redirectToHtml5JoinURL, redirectJoinURL) <- getRedirectUrls(liveMeeting, msg.body.userId, roomTo.externalId, roomTo.sequence.toString())
|
||||
} yield redirectToHtml5JoinURL
|
||||
).getOrElse("")
|
||||
|
||||
|
||||
BreakoutHdlrHelpers.sendChangeUserBreakoutMsg(
|
||||
outGW,
|
||||
meetingId,
|
||||
msg.body.userId,
|
||||
msg.body.fromBreakoutId,
|
||||
msg.body.toBreakoutId,
|
||||
redirectToHtml5JoinURL,
|
||||
)
|
||||
|
||||
//Send notification to moved User
|
||||
for {
|
||||
roomFrom <- breakoutModel.rooms.get(msg.body.fromBreakoutId)
|
||||
roomTo <- breakoutModel.rooms.get(msg.body.toBreakoutId)
|
||||
} yield {
|
||||
val notifyUserEvent = MsgBuilder.buildNotifyUserInMeetingEvtMsg(
|
||||
msg.body.userId,
|
||||
liveMeeting.props.meetingProp.intId,
|
||||
"info",
|
||||
"promote",
|
||||
"app.updateBreakoutRoom.userChangeRoomNotification",
|
||||
"Notification to warn user was moved to another room",
|
||||
Vector(roomTo.shortName)
|
||||
)
|
||||
outGW.send(notifyUserEvent)
|
||||
}
|
||||
}
|
||||
|
||||
state
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,123 @@
|
||||
package org.bigbluebutton.core.apps.breakout
|
||||
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.apps.{ BreakoutModel, PermissionCheck, RightsManagementTrait }
|
||||
import org.bigbluebutton.core.domain.{ BreakoutRoom2x, MeetingState2x }
|
||||
import org.bigbluebutton.core.models.PresentationInPod
|
||||
import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter }
|
||||
import org.bigbluebutton.core.running.MeetingActor
|
||||
|
||||
trait CreateBreakoutRoomsCmdMsgHdlr extends RightsManagementTrait {
|
||||
this: MeetingActor =>
|
||||
|
||||
val liveMeeting: LiveMeeting
|
||||
val outGW: OutMsgRouter
|
||||
|
||||
def handleCreateBreakoutRoomsCmdMsg(msg: CreateBreakoutRoomsCmdMsg, state: MeetingState2x): MeetingState2x = {
|
||||
|
||||
if (liveMeeting.props.meetingProp.disabledFeatures.contains("breakoutRooms")) {
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
val reason = "Breakout rooms is disabled for this meeting."
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, outGW, liveMeeting)
|
||||
state
|
||||
} else if (permissionFailed(PermissionCheck.MOD_LEVEL, PermissionCheck.VIEWER_LEVEL, liveMeeting.users2x, msg.header.userId) || liveMeeting.props.meetingProp.isBreakout) {
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
val reason = "No permission to create breakout room for meeting."
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId,
|
||||
reason, outGW, liveMeeting)
|
||||
state
|
||||
} else {
|
||||
state.breakout match {
|
||||
case Some(breakout) =>
|
||||
log.warning(
|
||||
"CreateBreakoutRooms event received while breakout created for meeting {}", liveMeeting.props.meetingProp.intId
|
||||
)
|
||||
state
|
||||
case None =>
|
||||
processRequest(msg, state)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def processRequest(msg: CreateBreakoutRoomsCmdMsg, state: MeetingState2x): MeetingState2x = {
|
||||
|
||||
val presId = getPresentationId(state)
|
||||
val presSlide = getPresentationSlide(state)
|
||||
val parentId = liveMeeting.props.meetingProp.intId
|
||||
var rooms = new collection.immutable.HashMap[String, BreakoutRoom2x]
|
||||
|
||||
var i = 0
|
||||
for (room <- msg.body.rooms) {
|
||||
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)
|
||||
rooms = rooms + (breakout.id -> breakout)
|
||||
}
|
||||
|
||||
for (breakout <- rooms.values.toVector) {
|
||||
val roomDetail = new BreakoutRoomDetail(
|
||||
breakout.id, breakout.name,
|
||||
liveMeeting.props.meetingProp.intId,
|
||||
breakout.sequence,
|
||||
breakout.shortName,
|
||||
breakout.isDefaultName,
|
||||
breakout.freeJoin,
|
||||
liveMeeting.props.voiceProp.dialNumber,
|
||||
breakout.voiceConf,
|
||||
msg.body.durationInMinutes * 60,
|
||||
liveMeeting.props.password.moderatorPass,
|
||||
liveMeeting.props.password.viewerPass,
|
||||
presId, presSlide, msg.body.record,
|
||||
liveMeeting.props.breakoutProps.privateChatEnabled,
|
||||
breakout.captureNotes,
|
||||
breakout.captureSlides,
|
||||
)
|
||||
|
||||
val event = buildCreateBreakoutRoomSysCmdMsg(liveMeeting.props.meetingProp.intId, roomDetail)
|
||||
outGW.send(event)
|
||||
}
|
||||
|
||||
val breakoutModel = new BreakoutModel(None, msg.body.durationInMinutes * 60, rooms)
|
||||
state.update(Some(breakoutModel))
|
||||
}
|
||||
|
||||
def buildCreateBreakoutRoomSysCmdMsg(meetingId: String, breakout: BreakoutRoomDetail): BbbCommonEnvCoreMsg = {
|
||||
val routing = collection.immutable.HashMap("sender" -> "bbb-apps-akka")
|
||||
val envelope = BbbCoreEnvelope(CreateBreakoutRoomSysCmdMsg.NAME, routing)
|
||||
val header = BbbCoreBaseHeader(CreateBreakoutRoomSysCmdMsg.NAME)
|
||||
|
||||
val body = CreateBreakoutRoomSysCmdMsgBody(meetingId, breakout)
|
||||
val event = CreateBreakoutRoomSysCmdMsg(header, body)
|
||||
BbbCommonEnvCoreMsg(envelope, event)
|
||||
}
|
||||
|
||||
def getPresentationId(state: MeetingState2x): String = {
|
||||
// in very rare cases the presentation conversion generates an error, what should we do?
|
||||
// those cases where default.pdf is deleted from the whiteboard
|
||||
var currentPresentation = "blank"
|
||||
for {
|
||||
defaultPod <- state.presentationPodManager.getDefaultPod()
|
||||
curPres <- defaultPod.getCurrentPresentation()
|
||||
} yield {
|
||||
currentPresentation = curPres.id
|
||||
}
|
||||
|
||||
currentPresentation
|
||||
}
|
||||
|
||||
def getPresentationSlide(state: MeetingState2x): Int = {
|
||||
if (!liveMeeting.presModel.getCurrentPage().isEmpty) liveMeeting.presModel.getCurrentPage().get.num else 0
|
||||
var currentSlide = 0
|
||||
for {
|
||||
defaultPod <- state.presentationPodManager.getDefaultPod()
|
||||
curPres <- defaultPod.getCurrentPresentation()
|
||||
curPage <- PresentationInPod.getCurrentPage(curPres)
|
||||
} yield {
|
||||
currentSlide = curPage.num
|
||||
}
|
||||
|
||||
currentSlide
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package org.bigbluebutton.core.apps.breakout
|
||||
|
||||
import org.bigbluebutton.core.api.EjectUserFromBreakoutInternalMsg
|
||||
import org.bigbluebutton.core.apps.users.UsersApp
|
||||
import org.bigbluebutton.core.models.{ RegisteredUsers }
|
||||
import org.bigbluebutton.core.running.{ LiveMeeting, MeetingActor, OutMsgRouter }
|
||||
import org.bigbluebutton.core2.message.senders.Sender
|
||||
|
||||
trait EjectUserFromBreakoutInternalMsgHdlr {
|
||||
this: MeetingActor =>
|
||||
|
||||
val liveMeeting: LiveMeeting
|
||||
|
||||
val outGW: OutMsgRouter
|
||||
|
||||
def handleEjectUserFromBreakoutInternalMsgHdlr(msg: EjectUserFromBreakoutInternalMsg) = {
|
||||
|
||||
for {
|
||||
registeredUser <- RegisteredUsers.findAllWithExternUserId(msg.extUserId, liveMeeting.registeredUsers)
|
||||
} yield {
|
||||
UsersApp.ejectUserFromMeeting(
|
||||
outGW,
|
||||
liveMeeting,
|
||||
registeredUser.id,
|
||||
msg.ejectedBy,
|
||||
msg.reason,
|
||||
msg.reasonCode,
|
||||
msg.ban
|
||||
)
|
||||
// send a system message to force disconnection
|
||||
Sender.sendDisconnectClientSysMsg(msg.breakoutId, registeredUser.id, msg.ejectedBy, msg.reasonCode, outGW)
|
||||
|
||||
//send users update to parent meeting
|
||||
BreakoutHdlrHelpers.updateParentMeetingWithUsers(liveMeeting, eventBus)
|
||||
|
||||
log.info("Eject user {} id={} in breakoutId {}", registeredUser.name, registeredUser.id, msg.breakoutId)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
package org.bigbluebutton.core.apps.breakout
|
||||
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.api.EndBreakoutRoomInternalMsg
|
||||
import org.bigbluebutton.core.bus.BigBlueButtonEvent
|
||||
import org.bigbluebutton.core.domain.{ MeetingEndReason, MeetingState2x }
|
||||
import org.bigbluebutton.core.running.{ MeetingActor, OutMsgRouter }
|
||||
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
|
||||
import org.bigbluebutton.core2.message.senders.{ MsgBuilder }
|
||||
|
||||
trait EndAllBreakoutRoomsMsgHdlr extends RightsManagementTrait {
|
||||
this: MeetingActor =>
|
||||
|
||||
val outGW: OutMsgRouter
|
||||
|
||||
def handleEndAllBreakoutRoomsMsg(msg: EndAllBreakoutRoomsMsg, state: MeetingState2x): MeetingState2x = {
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
if (permissionFailed(PermissionCheck.MOD_LEVEL, PermissionCheck.VIEWER_LEVEL, liveMeeting.users2x, msg.header.userId)) {
|
||||
val reason = "No permission to end breakout rooms for meeting."
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, outGW, liveMeeting)
|
||||
state
|
||||
} else {
|
||||
for {
|
||||
model <- state.breakout
|
||||
} yield {
|
||||
model.rooms.values.foreach { room =>
|
||||
eventBus.publish(BigBlueButtonEvent(room.id, EndBreakoutRoomInternalMsg(meetingId, room.id, MeetingEndReason.BREAKOUT_ENDED_BY_MOD)))
|
||||
}
|
||||
|
||||
val notifyEvent = MsgBuilder.buildNotifyAllInMeetingEvtMsg(
|
||||
meetingId,
|
||||
"info",
|
||||
"rooms",
|
||||
"app.toast.breakoutRoomEnded",
|
||||
"Message when the breakout room is ended",
|
||||
Vector()
|
||||
)
|
||||
outGW.send(notifyEvent)
|
||||
|
||||
}
|
||||
|
||||
state.update(None)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package org.bigbluebutton.core.apps.breakout
|
||||
|
||||
import org.bigbluebutton.core.api.{ CaptureSharedNotesReqInternalMsg, CapturePresentationReqInternalMsg, EndBreakoutRoomInternalMsg }
|
||||
import org.bigbluebutton.core.bus.{ BigBlueButtonEvent, InternalEventBus }
|
||||
import org.bigbluebutton.core.running.{ BaseMeetingActor, HandlerHelpers, LiveMeeting, OutMsgRouter }
|
||||
|
||||
trait EndBreakoutRoomInternalMsgHdlr extends HandlerHelpers {
|
||||
this: BaseMeetingActor =>
|
||||
|
||||
val liveMeeting: LiveMeeting
|
||||
val outGW: OutMsgRouter
|
||||
val eventBus: InternalEventBus
|
||||
|
||||
def handleEndBreakoutRoomInternalMsg(msg: EndBreakoutRoomInternalMsg): Unit = {
|
||||
|
||||
if (liveMeeting.props.breakoutProps.captureSlides) {
|
||||
val captureSlidesEvent = BigBlueButtonEvent(msg.breakoutId, CapturePresentationReqInternalMsg("system", msg.parentId))
|
||||
eventBus.publish(captureSlidesEvent)
|
||||
}
|
||||
|
||||
if (liveMeeting.props.breakoutProps.captureNotes) {
|
||||
val meetingName: String = liveMeeting.props.meetingProp.name
|
||||
val captureNotesEvent = BigBlueButtonEvent(msg.breakoutId, CaptureSharedNotesReqInternalMsg(msg.parentId, meetingName))
|
||||
eventBus.publish(captureNotesEvent)
|
||||
}
|
||||
|
||||
log.info("Breakout room {} ended by parent meeting {}.", msg.breakoutId, msg.parentId)
|
||||
sendEndMeetingDueToExpiry(msg.reason, eventBus, outGW, liveMeeting, "system")
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package org.bigbluebutton.core.apps.breakout
|
||||
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.domain.MeetingState2x
|
||||
import org.bigbluebutton.core.running.{ MeetingActor, OutMsgRouter }
|
||||
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
|
||||
import org.bigbluebutton.core.models.{ Users2x, Roles }
|
||||
|
||||
trait RequestBreakoutJoinURLReqMsgHdlr extends RightsManagementTrait {
|
||||
this: MeetingActor =>
|
||||
|
||||
val outGW: OutMsgRouter
|
||||
|
||||
def handleRequestBreakoutJoinURLReqMsg(msg: RequestBreakoutJoinURLReqMsg, state: MeetingState2x): MeetingState2x = {
|
||||
if (permissionFailed(PermissionCheck.GUEST_LEVEL, PermissionCheck.VIEWER_LEVEL, liveMeeting.users2x, msg.header.userId)) {
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
val reason = "No permission to request breakout room URL for meeting."
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, outGW, liveMeeting)
|
||||
} else {
|
||||
for {
|
||||
model <- state.breakout
|
||||
room <- model.find(msg.body.breakoutId)
|
||||
requesterUser <- Users2x.findWithIntId(liveMeeting.users2x, msg.header.userId)
|
||||
} yield {
|
||||
if (requesterUser.role == Roles.MODERATOR_ROLE || room.freeJoin) {
|
||||
BreakoutHdlrHelpers.sendJoinURL(
|
||||
liveMeeting,
|
||||
outGW,
|
||||
msg.body.userId,
|
||||
room.externalId,
|
||||
room.sequence.toString(),
|
||||
room.id
|
||||
)
|
||||
} else {
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
val reason = "No permission to request breakout room URL for meeting."
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, outGW, liveMeeting)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
state
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
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,18 @@
|
||||
package org.bigbluebutton.core.apps.breakout
|
||||
|
||||
import org.bigbluebutton.core.api.{ SendBreakoutUsersAuditInternalMsg }
|
||||
import org.bigbluebutton.core.running.{ MeetingActor, OutMsgRouter }
|
||||
|
||||
trait SendBreakoutUsersUpdateMsgHdlr {
|
||||
this: MeetingActor =>
|
||||
|
||||
val outGW: OutMsgRouter
|
||||
|
||||
def handleSendBreakoutUsersUpdateInternalMsg(msg: SendBreakoutUsersAuditInternalMsg): Unit = {
|
||||
|
||||
BreakoutHdlrHelpers.updateParentMeetingWithUsers(
|
||||
liveMeeting,
|
||||
eventBus
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
package org.bigbluebutton.core.apps.breakout
|
||||
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.api.{ SendMessageToBreakoutRoomInternalMsg }
|
||||
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
|
||||
import org.bigbluebutton.core.bus.BigBlueButtonEvent
|
||||
import org.bigbluebutton.core.domain.MeetingState2x
|
||||
import org.bigbluebutton.core.models.{ RegisteredUsers }
|
||||
import org.bigbluebutton.core.running.{ MeetingActor, OutMsgRouter }
|
||||
import org.bigbluebutton.core2.message.senders.{ MsgBuilder }
|
||||
|
||||
trait SendMessageToAllBreakoutRoomsMsgHdlr extends RightsManagementTrait {
|
||||
this: MeetingActor =>
|
||||
|
||||
val outGW: OutMsgRouter
|
||||
|
||||
def handleSendMessageToAllBreakoutRoomsMsg(msg: SendMessageToAllBreakoutRoomsReqMsg, state: MeetingState2x): MeetingState2x = {
|
||||
if (permissionFailed(PermissionCheck.MOD_LEVEL, PermissionCheck.VIEWER_LEVEL, liveMeeting.users2x, msg.header.userId)) {
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
val reason = "No permission to send message to all breakout rooms for meeting."
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, outGW, liveMeeting)
|
||||
state
|
||||
} else {
|
||||
for {
|
||||
breakoutModel <- state.breakout
|
||||
senderUser <- RegisteredUsers.findWithUserId(msg.header.userId, liveMeeting.registeredUsers)
|
||||
} yield {
|
||||
breakoutModel.rooms.values.foreach { room =>
|
||||
eventBus.publish(BigBlueButtonEvent(room.id, SendMessageToBreakoutRoomInternalMsg(props.breakoutProps.parentId, room.id, senderUser.name, msg.body.msg)))
|
||||
}
|
||||
|
||||
val event = buildSendMessageToAllBreakoutRoomsEvtMsg(msg.header.userId, msg.body.msg, breakoutModel.rooms.size)
|
||||
outGW.send(event)
|
||||
|
||||
val notifyModeratorEvent = MsgBuilder.buildNotifyUserInMeetingEvtMsg(
|
||||
msg.header.userId,
|
||||
liveMeeting.props.meetingProp.intId,
|
||||
"info",
|
||||
"group_chat",
|
||||
"app.createBreakoutRoom.msgToBreakoutsSent",
|
||||
"Message for chat sent successfully",
|
||||
Vector(s"${breakoutModel.rooms.size}")
|
||||
)
|
||||
outGW.send(notifyModeratorEvent)
|
||||
|
||||
log.debug("Sending message '{}' to all breakout rooms in meeting {}", msg.body.msg, props.meetingProp.intId)
|
||||
}
|
||||
|
||||
state
|
||||
}
|
||||
}
|
||||
|
||||
def buildSendMessageToAllBreakoutRoomsEvtMsg(senderId: String, msg: String, totalOfRooms: Int): BbbCommonEnvCoreMsg = {
|
||||
val routing = collection.immutable.HashMap("sender" -> "bbb-apps-akka")
|
||||
val envelope = BbbCoreEnvelope(SendMessageToAllBreakoutRoomsEvtMsg.NAME, routing)
|
||||
val header = BbbClientMsgHeader(SendMessageToAllBreakoutRoomsEvtMsg.NAME, liveMeeting.props.meetingProp.intId, "not-used")
|
||||
|
||||
val body = SendMessageToAllBreakoutRoomsEvtMsgBody(props.meetingProp.intId, senderId, msg, totalOfRooms)
|
||||
val event = SendMessageToAllBreakoutRoomsEvtMsg(header, body)
|
||||
BbbCommonEnvCoreMsg(envelope, event)
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package org.bigbluebutton.core.apps.breakout
|
||||
|
||||
import org.bigbluebutton.common2.msgs.{ GroupChatAccess, GroupChatMsgFromUser }
|
||||
import org.bigbluebutton.core.api.SendMessageToBreakoutRoomInternalMsg
|
||||
import org.bigbluebutton.core.apps.groupchats.GroupChatApp
|
||||
import org.bigbluebutton.core.bus.MessageBus
|
||||
import org.bigbluebutton.core.domain.MeetingState2x
|
||||
import org.bigbluebutton.core.models.SystemUser
|
||||
import org.bigbluebutton.core.running.{ LiveMeeting, MeetingActor, OutMsgRouter }
|
||||
|
||||
trait SendMessageToBreakoutRoomInternalMsgHdlr {
|
||||
this: MeetingActor =>
|
||||
|
||||
val outGW: OutMsgRouter
|
||||
|
||||
def handleSendMessageToBreakoutRoomInternalMsg(msg: SendMessageToBreakoutRoomInternalMsg, state: MeetingState2x, liveMeeting: LiveMeeting, bus: MessageBus): MeetingState2x = {
|
||||
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.senderName), true, msg.msg)
|
||||
val gcm = GroupChatApp.toGroupChatMessage(sender.copy(name = msg.senderName), groupChatMsgFromUser)
|
||||
val gcs = GroupChatApp.addGroupChatMessage(chat, state.groupChats, gcm)
|
||||
|
||||
val event = buildGroupChatMessageBroadcastEvtMsg(
|
||||
liveMeeting.props.meetingProp.intId,
|
||||
msg.senderName, GroupChatApp.MAIN_PUBLIC_CHAT, gcm
|
||||
)
|
||||
|
||||
bus.outGW.send(event)
|
||||
|
||||
state.update(gcs)
|
||||
}
|
||||
|
||||
newState match {
|
||||
case Some(ns) => ns
|
||||
case None => state
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
package org.bigbluebutton.core.apps.breakout
|
||||
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.apps.{ BreakoutModel, PermissionCheck, RightsManagementTrait }
|
||||
import org.bigbluebutton.core.domain.MeetingState2x
|
||||
import org.bigbluebutton.core.models.VoiceUsers
|
||||
import org.bigbluebutton.core.running.{ MeetingActor, OutMsgRouter }
|
||||
|
||||
trait TransferUserToMeetingRequestHdlr extends RightsManagementTrait {
|
||||
this: MeetingActor =>
|
||||
|
||||
val outGW: OutMsgRouter
|
||||
|
||||
def handleTransferUserToMeetingRequestMsg(msg: TransferUserToMeetingRequestMsg, state: MeetingState2x): MeetingState2x = {
|
||||
if (permissionFailed(PermissionCheck.MOD_LEVEL, PermissionCheck.VIEWER_LEVEL, liveMeeting.users2x, msg.header.userId)) {
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
val reason = "No permission to transfer user to voice breakout."
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, outGW, liveMeeting)
|
||||
} else {
|
||||
processRequest(msg)
|
||||
}
|
||||
|
||||
state
|
||||
}
|
||||
|
||||
def processRequest(msg: TransferUserToMeetingRequestMsg) {
|
||||
if (msg.body.fromMeetingId == liveMeeting.props.meetingProp.intId) {
|
||||
// want to transfer from parent meeting to breakout
|
||||
for {
|
||||
model <- state.breakout
|
||||
to <- getVoiceConf(msg.body.toMeetingId, model)
|
||||
from <- getVoiceConf(msg.body.fromMeetingId, model)
|
||||
voiceUser <- VoiceUsers.findWithIntId(liveMeeting.voiceUsers, msg.body.userId)
|
||||
} yield {
|
||||
val event = buildTransferUserToVoiceConfSysMsg(from, to, voiceUser.voiceUserId)
|
||||
outGW.send(event)
|
||||
}
|
||||
} else {
|
||||
|
||||
for {
|
||||
model <- state.breakout
|
||||
room <- model.find(msg.body.fromMeetingId)
|
||||
} yield {
|
||||
room.voiceUsers.foreach { vu =>
|
||||
log.info(" ***** Breakout voice user={} userId={}", vu, msg.body.userId)
|
||||
}
|
||||
}
|
||||
|
||||
for {
|
||||
model <- state.breakout
|
||||
to <- getVoiceConf(msg.body.toMeetingId, model)
|
||||
from <- getVoiceConf(msg.body.fromMeetingId, model)
|
||||
room <- model.find(msg.body.fromMeetingId)
|
||||
voiceUser <- room.voiceUsers.find(p => p.id == msg.body.userId)
|
||||
} yield {
|
||||
val event = buildTransferUserToVoiceConfSysMsg(from, to, voiceUser.voiceUserId)
|
||||
outGW.send(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def buildTransferUserToVoiceConfSysMsg(fromVoiceConf: String, toVoiceConf: String, voiceUserId: String): BbbCommonEnvCoreMsg = {
|
||||
val routing = collection.immutable.HashMap("sender" -> "bbb-apps-akka")
|
||||
val envelope = BbbCoreEnvelope(TransferUserToVoiceConfSysMsg.NAME, routing)
|
||||
val header = BbbCoreHeaderWithMeetingId(TransferUserToVoiceConfSysMsg.NAME, props.meetingProp.intId)
|
||||
|
||||
val body = TransferUserToVoiceConfSysMsgBody(fromVoiceConf, toVoiceConf, voiceUserId)
|
||||
val event = TransferUserToVoiceConfSysMsg(header, body)
|
||||
BbbCommonEnvCoreMsg(envelope, event)
|
||||
}
|
||||
|
||||
def getVoiceConf(meetingId: String, breakoutModel: BreakoutModel): Option[String] = {
|
||||
if (meetingId == liveMeeting.props.meetingProp.intId) {
|
||||
Some(liveMeeting.props.voiceProp.voiceConf)
|
||||
} else {
|
||||
for {
|
||||
room <- breakoutModel.find(meetingId)
|
||||
} yield room.voiceConf
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package org.bigbluebutton.core.apps.breakout
|
||||
|
||||
import org.bigbluebutton.core.api.{ UpdateBreakoutRoomTimeInternalMsg }
|
||||
import org.bigbluebutton.core.domain.{ MeetingState2x }
|
||||
import org.bigbluebutton.core.running.{ MeetingActor, OutMsgRouter }
|
||||
|
||||
trait UpdateBreakoutRoomTimeInternalMsgHdlr {
|
||||
this: MeetingActor =>
|
||||
|
||||
val outGW: OutMsgRouter
|
||||
|
||||
def handleUpdateBreakoutRoomTimeInternalMsgHdlr(msg: UpdateBreakoutRoomTimeInternalMsg, state: MeetingState2x): MeetingState2x = {
|
||||
|
||||
val breakoutModel = for {
|
||||
model <- state.breakout
|
||||
} yield {
|
||||
val updatedBreakoutModel = model.setTime(msg.durationInSeconds)
|
||||
updatedBreakoutModel
|
||||
}
|
||||
|
||||
breakoutModel match {
|
||||
case Some(model) => state.update(Some(model))
|
||||
case None => state
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,106 @@
|
||||
package org.bigbluebutton.core.apps.breakout
|
||||
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.api.{ UpdateBreakoutRoomTimeInternalMsg, SendTimeRemainingAuditInternalMsg }
|
||||
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
|
||||
import org.bigbluebutton.core.bus.BigBlueButtonEvent
|
||||
import org.bigbluebutton.core.domain.MeetingState2x
|
||||
import org.bigbluebutton.core.running.{ MeetingActor, OutMsgRouter }
|
||||
import org.bigbluebutton.core2.message.senders.{ MsgBuilder, Sender }
|
||||
import org.bigbluebutton.core.util.TimeUtil
|
||||
|
||||
trait UpdateBreakoutRoomsTimeMsgHdlr extends RightsManagementTrait {
|
||||
this: MeetingActor =>
|
||||
|
||||
val outGW: OutMsgRouter
|
||||
|
||||
def handleUpdateBreakoutRoomsTimeMsg(msg: UpdateBreakoutRoomsTimeReqMsg, state: MeetingState2x): MeetingState2x = {
|
||||
|
||||
if (permissionFailed(PermissionCheck.MOD_LEVEL, PermissionCheck.VIEWER_LEVEL, liveMeeting.users2x, msg.header.userId)) {
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
val reason = "No permission to update time for breakout rooms for meeting."
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, outGW, liveMeeting)
|
||||
state
|
||||
} else if (msg.body.timeInMinutes <= 0) {
|
||||
log.error("Error while trying to update {} minutes for breakout rooms time in meeting {}. Only positive values are allowed!", msg.body.timeInMinutes, props.meetingProp.intId)
|
||||
state
|
||||
} else {
|
||||
val updatedModel = for {
|
||||
breakoutModel <- state.breakout
|
||||
startedOn <- breakoutModel.startedOn
|
||||
} yield {
|
||||
val newSecsRemaining = msg.body.timeInMinutes * 60
|
||||
val breakoutRoomSecsElapsed = TimeUtil.millisToSeconds(System.currentTimeMillis()) - TimeUtil.millisToSeconds(startedOn);
|
||||
val newDurationInSeconds = breakoutRoomSecsElapsed.toInt + newSecsRemaining
|
||||
|
||||
var isNewTimeHigherThanMeetingRemaining = false
|
||||
|
||||
if (state.expiryTracker.durationInMs > 0) {
|
||||
val mainRoomEndTime = state.expiryTracker.startedOnInMs + state.expiryTracker.durationInMs
|
||||
val mainRoomSecsRemaining = TimeUtil.millisToSeconds(mainRoomEndTime - TimeUtil.timeNowInMs())
|
||||
val mainRoomTimeRemainingInMinutes = mainRoomSecsRemaining / 60
|
||||
|
||||
//Avoid breakout room end later than main room
|
||||
//Keep 5 seconds of margin to finish breakout room and send informations to parent meeting
|
||||
if (newSecsRemaining > (mainRoomSecsRemaining - 5)) {
|
||||
log.error("Error while trying to update {} minutes for breakout rooms time in meeting {}. Parent meeting will end up in {} minutes!", msg.body.timeInMinutes, props.meetingProp.intId, mainRoomTimeRemainingInMinutes)
|
||||
isNewTimeHigherThanMeetingRemaining = true
|
||||
}
|
||||
}
|
||||
|
||||
if (isNewTimeHigherThanMeetingRemaining) {
|
||||
breakoutModel
|
||||
} else {
|
||||
breakoutModel.rooms.values.foreach { room =>
|
||||
eventBus.publish(BigBlueButtonEvent(room.id, UpdateBreakoutRoomTimeInternalMsg(props.breakoutProps.parentId, room.id, newDurationInSeconds)))
|
||||
val notifyEvent = MsgBuilder.buildNotifyAllInMeetingEvtMsg(
|
||||
room.id,
|
||||
"info",
|
||||
"about",
|
||||
"app.chat.breakoutDurationUpdated",
|
||||
"Used when the breakout duration is updated",
|
||||
Vector(s"${msg.body.timeInMinutes}")
|
||||
)
|
||||
outGW.send(notifyEvent)
|
||||
}
|
||||
|
||||
val notifyModeratorEvent = MsgBuilder.buildNotifyUserInMeetingEvtMsg(
|
||||
msg.header.userId,
|
||||
liveMeeting.props.meetingProp.intId,
|
||||
"info",
|
||||
"promote",
|
||||
"app.chat.breakoutDurationUpdatedModerator",
|
||||
"Sent to the moderator that requested breakout duration change",
|
||||
Vector(s"${msg.body.timeInMinutes}")
|
||||
)
|
||||
outGW.send(notifyModeratorEvent)
|
||||
|
||||
log.debug("Updating {} minutes for breakout rooms time in meeting {}", msg.body.timeInMinutes, props.meetingProp.intId)
|
||||
breakoutModel.setTime(newDurationInSeconds)
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
case None => state
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def buildUpdateBreakoutRoomsTimeEvtMsg(timeInMinutes: Int): BbbCommonEnvCoreMsg = {
|
||||
val routing = collection.immutable.HashMap("sender" -> "bbb-apps-akka")
|
||||
val envelope = BbbCoreEnvelope(UpdateBreakoutRoomsTimeEvtMsg.NAME, routing)
|
||||
val header = BbbClientMsgHeader(UpdateBreakoutRoomsTimeEvtMsg.NAME, liveMeeting.props.meetingProp.intId, "not-used")
|
||||
|
||||
val body = UpdateBreakoutRoomsTimeEvtMsgBody(props.meetingProp.intId, timeInMinutes)
|
||||
val event = UpdateBreakoutRoomsTimeEvtMsg(header, body)
|
||||
BbbCommonEnvCoreMsg(envelope, event)
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,118 @@
|
||||
package org.bigbluebutton.core.apps.caption
|
||||
|
||||
import akka.actor.ActorContext
|
||||
import akka.event.Logging
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.bus.MessageBus
|
||||
import org.bigbluebutton.core.running.{ LiveMeeting }
|
||||
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
|
||||
|
||||
class CaptionApp2x(implicit val context: ActorContext) extends RightsManagementTrait {
|
||||
val log = Logging(context.system, getClass)
|
||||
|
||||
def getCaptionHistory(liveMeeting: LiveMeeting): Map[String, TranscriptVO] = {
|
||||
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)
|
||||
}
|
||||
|
||||
def handle(msg: EditCaptionHistoryPubMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
|
||||
def broadcastEvent(msg: EditCaptionHistoryPubMsg): Unit = {
|
||||
val routing = Routing.addMsgToClientRouting(
|
||||
MessageTypes.BROADCAST_TO_MEETING,
|
||||
liveMeeting.props.meetingProp.intId, msg.header.userId
|
||||
)
|
||||
val envelope = BbbCoreEnvelope(EditCaptionHistoryEvtMsg.NAME, routing)
|
||||
val header = BbbClientMsgHeader(EditCaptionHistoryEvtMsg.NAME, liveMeeting.props.meetingProp.intId, msg.header.userId)
|
||||
|
||||
val body = EditCaptionHistoryEvtMsgBody(msg.body.startIndex, msg.body.endIndex, msg.body.name, msg.body.locale, msg.body.text)
|
||||
val event = EditCaptionHistoryEvtMsg(header, body)
|
||||
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
|
||||
bus.outGW.send(msgEvent)
|
||||
}
|
||||
|
||||
if (permissionFailed(PermissionCheck.MOD_LEVEL, PermissionCheck.VIEWER_LEVEL, liveMeeting.users2x, msg.header.userId)
|
||||
&& isUserCaptionOwner(liveMeeting, msg.header.userId, msg.body.name)) {
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
val reason = "No permission to edit caption history in meeting."
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting)
|
||||
} else {
|
||||
val successfulEdit = editCaptionHistory(liveMeeting, msg.header.userId, msg.body.startIndex,
|
||||
msg.body.endIndex, msg.body.name, msg.body.text)
|
||||
if (successfulEdit) {
|
||||
broadcastEvent(msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
val envelope = BbbCoreEnvelope(SendCaptionHistoryRespMsg.NAME, routing)
|
||||
val header = BbbClientMsgHeader(SendCaptionHistoryRespMsg.NAME, liveMeeting.props.meetingProp.intId, msg.header.userId)
|
||||
|
||||
val body = SendCaptionHistoryRespMsgBody(history)
|
||||
val event = SendCaptionHistoryRespMsg(header, body)
|
||||
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
|
||||
bus.outGW.send(msgEvent)
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
val body = UpdateCaptionOwnerEvtMsgBody(name, locale, newOwnerId)
|
||||
val event = UpdateCaptionOwnerEvtMsg(header, body)
|
||||
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
|
||||
bus.outGW.send(msgEvent)
|
||||
}
|
||||
|
||||
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."
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package org.bigbluebutton.core.apps.chat
|
||||
|
||||
import akka.actor.ActorContext
|
||||
|
||||
class ChatApp2x(implicit val context: ActorContext)
|
||||
extends GetChatHistoryReqMsgHdlr
|
||||
with SendPublicMessagePubMsgHdlr
|
||||
with SendPrivateMessagePubMsgHdlr
|
||||
with ClearPublicChatHistoryPubMsgHdlr
|
||||
with UserTypingPubMsgHdlr {
|
||||
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
package org.bigbluebutton.core.apps.chat
|
||||
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.apps.{ ChatModel, PermissionCheck, RightsManagementTrait }
|
||||
import org.bigbluebutton.core.bus.MessageBus
|
||||
import org.bigbluebutton.core.running.{ LiveMeeting, LogHelper }
|
||||
import org.bigbluebutton.core.domain.MeetingState2x
|
||||
|
||||
trait ClearPublicChatHistoryPubMsgHdlr extends LogHelper with RightsManagementTrait {
|
||||
|
||||
def handle(msg: ClearPublicChatHistoryPubMsg, state: MeetingState2x,
|
||||
liveMeeting: LiveMeeting, bus: MessageBus): MeetingState2x = {
|
||||
def broadcastEvent(msg: ClearPublicChatHistoryPubMsg): Unit = {
|
||||
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, liveMeeting.props.meetingProp.intId, msg.header.userId)
|
||||
val envelope = BbbCoreEnvelope(ClearPublicChatHistoryEvtMsg.NAME, routing)
|
||||
val header = BbbClientMsgHeader(ClearPublicChatHistoryEvtMsg.NAME, liveMeeting.props.meetingProp.intId, msg.header.userId)
|
||||
|
||||
val body = ClearPublicChatHistoryEvtMsgBody(msg.body.chatId)
|
||||
val event = ClearPublicChatHistoryEvtMsg(header, body)
|
||||
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
|
||||
bus.outGW.send(msgEvent)
|
||||
}
|
||||
|
||||
if (permissionFailed(PermissionCheck.MOD_LEVEL, PermissionCheck.VIEWER_LEVEL, liveMeeting.users2x, msg.header.userId)) {
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
val reason = "No permission to clear chat in meeting."
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting)
|
||||
state
|
||||
} else {
|
||||
ChatModel.clearPublicChatHistory(liveMeeting.chatModel)
|
||||
val newState = for {
|
||||
gc <- state.groupChats.find(msg.body.chatId)
|
||||
} yield {
|
||||
broadcastEvent(msg)
|
||||
val newGc = gc.clearMessages()
|
||||
val gcs = state.groupChats.update(newGc)
|
||||
state.update(gcs)
|
||||
}
|
||||
|
||||
newState match {
|
||||
case Some(ns) => ns
|
||||
case None => state
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package org.bigbluebutton.core.apps.chat
|
||||
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.apps.ChatModel
|
||||
import org.bigbluebutton.core.bus.MessageBus
|
||||
import org.bigbluebutton.core.running.{ LiveMeeting, LogHelper }
|
||||
|
||||
trait GetChatHistoryReqMsgHdlr extends LogHelper {
|
||||
|
||||
def handle(msg: GetChatHistoryReqMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
|
||||
log.debug(" GOT CHAT HISTORY")
|
||||
|
||||
def broadcastEvent(msg: GetChatHistoryReqMsg, history: Array[ChatMessageVO]): Unit = {
|
||||
val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, liveMeeting.props.meetingProp.intId, msg.header.userId)
|
||||
val envelope = BbbCoreEnvelope(GetChatHistoryRespMsg.NAME, routing)
|
||||
val header = BbbClientMsgHeader(GetChatHistoryRespMsg.NAME, liveMeeting.props.meetingProp.intId, msg.header.userId)
|
||||
|
||||
val body = GetChatHistoryRespMsgBody(history)
|
||||
val event = GetChatHistoryRespMsg(header, body)
|
||||
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
|
||||
bus.outGW.send(msgEvent)
|
||||
}
|
||||
|
||||
val history = ChatModel.getChatHistory(liveMeeting.chatModel)
|
||||
broadcastEvent(msg, history)
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package org.bigbluebutton.core.apps.chat
|
||||
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.bus.MessageBus
|
||||
import org.bigbluebutton.core.running.{ LiveMeeting, LogHelper }
|
||||
|
||||
trait SendPrivateMessagePubMsgHdlr extends LogHelper {
|
||||
|
||||
def handle(msg: SendPrivateMessagePubMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
|
||||
log.debug("SENDING PRIVATE CHAT MESSAGE")
|
||||
|
||||
def broadcastEvent(message: ChatMessageVO, userId: String): Unit = {
|
||||
val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, liveMeeting.props.meetingProp.intId, userId)
|
||||
val envelope = BbbCoreEnvelope(SendPrivateMessageEvtMsg.NAME, routing)
|
||||
val header = BbbClientMsgHeader(SendPrivateMessageEvtMsg.NAME, liveMeeting.props.meetingProp.intId, userId)
|
||||
|
||||
val body = SendPrivateMessageEvtMsgBody(message)
|
||||
val event = SendPrivateMessageEvtMsg(header, body)
|
||||
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
|
||||
bus.outGW.send(msgEvent)
|
||||
}
|
||||
|
||||
broadcastEvent(msg.body.message, msg.body.message.fromUserId)
|
||||
broadcastEvent(msg.body.message, msg.body.message.toUserId)
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package org.bigbluebutton.core.apps.chat
|
||||
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.apps.ChatModel
|
||||
import org.bigbluebutton.core.bus.MessageBus
|
||||
import org.bigbluebutton.core.running.{ LiveMeeting, LogHelper }
|
||||
|
||||
trait SendPublicMessagePubMsgHdlr extends LogHelper {
|
||||
|
||||
def handle(msg: SendPublicMessagePubMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
|
||||
log.debug("SENDING CHAT MESSAGE")
|
||||
|
||||
def broadcastEvent(msg: SendPublicMessagePubMsg, message: ChatMessageVO): Unit = {
|
||||
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, liveMeeting.props.meetingProp.intId, msg.header.userId)
|
||||
val envelope = BbbCoreEnvelope(SendPublicMessageEvtMsg.NAME, routing)
|
||||
val header = BbbClientMsgHeader(SendPublicMessageEvtMsg.NAME, liveMeeting.props.meetingProp.intId, msg.header.userId)
|
||||
|
||||
val body = SendPublicMessageEvtMsgBody(msg.body.message)
|
||||
val event = SendPublicMessageEvtMsg(header, body)
|
||||
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
|
||||
bus.outGW.send(msgEvent)
|
||||
}
|
||||
|
||||
ChatModel.addNewChatMessage(liveMeeting.chatModel, msg.body.message)
|
||||
broadcastEvent(msg, msg.body.message)
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package org.bigbluebutton.core.apps.chat
|
||||
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.bus.MessageBus
|
||||
import org.bigbluebutton.core.running.{ LiveMeeting, LogHelper }
|
||||
|
||||
trait UserTypingPubMsgHdlr extends LogHelper {
|
||||
def handle(msg: UserTypingPubMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
|
||||
def broadcastEvent(msg: UserTypingPubMsg): Unit = {
|
||||
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, liveMeeting.props.meetingProp.intId, msg.header.userId)
|
||||
val envelope = BbbCoreEnvelope(UserTypingEvtMsg.NAME, routing)
|
||||
val header = BbbClientMsgHeader(UserTypingEvtMsg.NAME, liveMeeting.props.meetingProp.intId, msg.header.userId)
|
||||
|
||||
val body = UserTypingEvtMsgBody(msg.body.chatId, msg.header.userId)
|
||||
val event = UserTypingEvtMsg(header, body)
|
||||
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
|
||||
bus.outGW.send(msgEvent)
|
||||
}
|
||||
broadcastEvent(msg)
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package org.bigbluebutton.core.apps.externalvideo
|
||||
|
||||
import akka.actor.ActorContext
|
||||
import akka.event.Logging
|
||||
|
||||
class ExternalVideoApp2x(implicit val context: ActorContext)
|
||||
extends StartExternalVideoPubMsgHdlr
|
||||
with UpdateExternalVideoPubMsgHdlr
|
||||
with StopExternalVideoPubMsgHdlr {
|
||||
|
||||
val log = Logging(context.system, getClass)
|
||||
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
package org.bigbluebutton.core.apps.externalvideo
|
||||
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.apps.{ ExternalVideoModel, PermissionCheck, RightsManagementTrait }
|
||||
import org.bigbluebutton.core.bus.MessageBus
|
||||
import org.bigbluebutton.core.running.LiveMeeting
|
||||
import org.bigbluebutton.core.apps.screenshare.ScreenshareApp2x.{ requestBroadcastStop }
|
||||
|
||||
trait StartExternalVideoPubMsgHdlr extends RightsManagementTrait {
|
||||
this: ExternalVideoApp2x =>
|
||||
|
||||
def handle(msg: StartExternalVideoPubMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
|
||||
log.info("Received StartExternalVideoPubMsgr meetingId={} url={}", liveMeeting.props.meetingProp.intId, msg.body.externalVideoUrl)
|
||||
|
||||
def broadcastEvent(msg: StartExternalVideoPubMsg) {
|
||||
|
||||
val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, liveMeeting.props.meetingProp.intId, "nodeJSapp")
|
||||
val envelope = BbbCoreEnvelope(StartExternalVideoEvtMsg.NAME, routing)
|
||||
val header = BbbClientMsgHeader(StartExternalVideoEvtMsg.NAME, liveMeeting.props.meetingProp.intId, msg.header.userId)
|
||||
|
||||
val body = StartExternalVideoEvtMsgBody(msg.body.externalVideoUrl)
|
||||
val event = StartExternalVideoEvtMsg(header, body)
|
||||
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
|
||||
bus.outGW.send(msgEvent)
|
||||
}
|
||||
|
||||
if (liveMeeting.props.meetingProp.disabledFeatures.contains("externalVideos")) {
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
val reason = "External Videos is disabled for this meeting."
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting)
|
||||
} else if (permissionFailed(PermissionCheck.GUEST_LEVEL, PermissionCheck.PRESENTER_LEVEL, liveMeeting.users2x, msg.header.userId)) {
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
val reason = "You need to be the presenter to start external videos"
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting)
|
||||
} else {
|
||||
|
||||
// Request a screen broadcast stop (goes to SFU, comes back through
|
||||
// ScreenshareRtmpBroadcastStoppedVoiceConfEvtMsg)
|
||||
requestBroadcastStop(bus.outGW, liveMeeting)
|
||||
|
||||
ExternalVideoModel.setURL(liveMeeting.externalVideoModel, msg.body.externalVideoUrl)
|
||||
broadcastEvent(msg)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package org.bigbluebutton.core.apps.externalvideo
|
||||
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.apps.{ ExternalVideoModel, PermissionCheck, RightsManagementTrait }
|
||||
import org.bigbluebutton.core.bus.MessageBus
|
||||
import org.bigbluebutton.core.running.LiveMeeting
|
||||
import org.bigbluebutton.core2.message.senders.MsgBuilder
|
||||
|
||||
trait StopExternalVideoPubMsgHdlr extends RightsManagementTrait {
|
||||
this: ExternalVideoApp2x =>
|
||||
|
||||
def handle(msg: StopExternalVideoPubMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
|
||||
log.info("Received StopExternalVideoPubMsgr meetingId={}", liveMeeting.props.meetingProp.intId)
|
||||
|
||||
if (permissionFailed(PermissionCheck.GUEST_LEVEL, PermissionCheck.PRESENTER_LEVEL, liveMeeting.users2x, msg.header.userId)) {
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
val reason = "You need to be the presenter to stop external video"
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting)
|
||||
} else {
|
||||
ExternalVideoModel.clear(liveMeeting.externalVideoModel)
|
||||
|
||||
//broadcastEvent
|
||||
val msgEvent = MsgBuilder.buildStopExternalVideoEvtMsg(liveMeeting.props.meetingProp.intId, msg.header.userId)
|
||||
bus.outGW.send(msgEvent)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package org.bigbluebutton.core.apps.externalvideo
|
||||
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
|
||||
import org.bigbluebutton.core.bus.MessageBus
|
||||
import org.bigbluebutton.core.running.{ LiveMeeting }
|
||||
|
||||
trait UpdateExternalVideoPubMsgHdlr extends RightsManagementTrait {
|
||||
|
||||
def handle(msg: UpdateExternalVideoPubMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
|
||||
def broadcastEvent(msg: UpdateExternalVideoPubMsg) {
|
||||
val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, liveMeeting.props.meetingProp.intId, "nodeJSapp")
|
||||
val envelope = BbbCoreEnvelope(UpdateExternalVideoEvtMsg.NAME, routing)
|
||||
val header = BbbClientMsgHeader(UpdateExternalVideoEvtMsg.NAME, liveMeeting.props.meetingProp.intId, msg.header.userId)
|
||||
|
||||
val body = UpdateExternalVideoEvtMsgBody(msg.body.status, msg.body.rate, msg.body.time, msg.body.state)
|
||||
val event = UpdateExternalVideoEvtMsg(header, body)
|
||||
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
|
||||
bus.outGW.send(msgEvent)
|
||||
}
|
||||
|
||||
if (permissionFailed(PermissionCheck.GUEST_LEVEL, PermissionCheck.PRESENTER_LEVEL, liveMeeting.users2x, msg.header.userId)) {
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
val reason = "You need to be the presenter to update external video"
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting)
|
||||
} else {
|
||||
broadcastEvent(msg)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package org.bigbluebutton.core.apps.groupchats
|
||||
|
||||
trait ChangeGroupChatAccessReqMsgHdlr {
|
||||
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
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.GroupChat
|
||||
import org.bigbluebutton.core.running.LiveMeeting
|
||||
import org.bigbluebutton.core.models.SystemUser
|
||||
|
||||
trait CreateDefaultPublicGroupChat {
|
||||
this: GroupChatHdlrs =>
|
||||
|
||||
def handleCreateDefaultPublicGroupChat(state: MeetingState2x, liveMeeting: LiveMeeting, bus: MessageBus): MeetingState2x = {
|
||||
|
||||
val groupChat: GroupChat = GroupChatApp.createDefaultPublicGroupChat()
|
||||
|
||||
def buildGroupChatCreatedEvtMsg(meetingId: String, userId: String, gc: GroupChat): BbbCommonEnvCoreMsg = {
|
||||
val correlationId = "SYSTEM-" + System.currentTimeMillis()
|
||||
val msgs = gc.msgs.map(m => GroupChatApp.toMessageToUser(m))
|
||||
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, meetingId, userId)
|
||||
val envelope = BbbCoreEnvelope(GroupChatCreatedEvtMsg.NAME, routing)
|
||||
val header = BbbClientMsgHeader(GroupChatCreatedEvtMsg.NAME, meetingId, userId)
|
||||
val body = GroupChatCreatedEvtMsgBody(correlationId, gc.id, gc.createdBy, gc.access, gc.users, msgs)
|
||||
val event = GroupChatCreatedEvtMsg(header, body)
|
||||
BbbCommonEnvCoreMsg(envelope, event)
|
||||
}
|
||||
|
||||
val respMsg = buildGroupChatCreatedEvtMsg(
|
||||
liveMeeting.props.meetingProp.intId,
|
||||
SystemUser.ID,
|
||||
groupChat
|
||||
)
|
||||
|
||||
bus.outGW.send(respMsg)
|
||||
val groupChats = state.groupChats.add(groupChat)
|
||||
state.update(groupChats)
|
||||
}
|
||||
}
|
@ -0,0 +1,130 @@
|
||||
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.GroupChat
|
||||
import org.bigbluebutton.core.running.LiveMeeting
|
||||
import org.bigbluebutton.core.apps.PermissionCheck
|
||||
import org.bigbluebutton.SystemConfiguration
|
||||
import org.bigbluebutton.core.models.Users2x
|
||||
import org.bigbluebutton.core.models.Roles
|
||||
import org.bigbluebutton.core2.MeetingStatus2x
|
||||
|
||||
trait CreateGroupChatReqMsgHdlr extends SystemConfiguration {
|
||||
this: GroupChatHdlrs =>
|
||||
|
||||
def handle(msg: CreateGroupChatReqMsg, state: MeetingState2x,
|
||||
liveMeeting: LiveMeeting, bus: MessageBus): MeetingState2x = {
|
||||
log.debug("RECEIVED CREATE CHAT REQ MESSAGE")
|
||||
|
||||
var chatLocked: Boolean = false
|
||||
|
||||
for {
|
||||
user <- Users2x.findWithIntId(liveMeeting.users2x, msg.header.userId)
|
||||
} yield {
|
||||
if (user.role != Roles.MODERATOR_ROLE) {
|
||||
if (msg.body.access == GroupChatAccess.PRIVATE) {
|
||||
val permissions = MeetingStatus2x.getPermissions(liveMeeting.status)
|
||||
val modMembers = msg.body.users.filter(userId => Users2x.findWithIntId(liveMeeting.users2x, userId) match {
|
||||
case Some(user) => user.role == Roles.MODERATOR_ROLE
|
||||
case None => false
|
||||
})
|
||||
// don't lock creation of private chats that involve a moderator
|
||||
if (modMembers.length == 0) {
|
||||
chatLocked = user.locked && permissions.disablePrivChat
|
||||
}
|
||||
} else {
|
||||
chatLocked = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if this message was sent while the lock settings was being changed.
|
||||
val isDelayedMessage = System.currentTimeMillis() - MeetingStatus2x.getPermissionsChangedOn(liveMeeting.status) < 5000
|
||||
|
||||
if (applyPermissionCheck && chatLocked && !isDelayedMessage) {
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
val reason = "No permission to create a new group chat."
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting)
|
||||
state
|
||||
} else {
|
||||
val newState = for {
|
||||
createdBy <- GroupChatApp.findGroupChatUser(msg.header.userId, liveMeeting.users2x)
|
||||
} yield {
|
||||
val msgs = msg.body.msg.map(m => GroupChatApp.toGroupChatMessage(createdBy, m))
|
||||
val users = {
|
||||
if (msg.body.access == GroupChatAccess.PRIVATE) {
|
||||
val cu = msg.body.users.toSet + msg.header.userId
|
||||
cu.flatMap(u => GroupChatApp.findGroupChatUser(u, liveMeeting.users2x)).toVector
|
||||
} else {
|
||||
Vector.empty
|
||||
}
|
||||
}
|
||||
|
||||
val gc = GroupChatApp.createGroupChat(msg.body.access, createdBy, users, msgs)
|
||||
sendMessages(msg, gc, liveMeeting, bus)
|
||||
|
||||
val groupChats = state.groupChats.add(gc)
|
||||
state.update(groupChats)
|
||||
}
|
||||
|
||||
newState.getOrElse(state)
|
||||
}
|
||||
}
|
||||
|
||||
def sendMessages(msg: CreateGroupChatReqMsg, gc: GroupChat,
|
||||
liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
|
||||
def makeHeader(name: String, meetingId: String, userId: String): BbbClientMsgHeader = {
|
||||
BbbClientMsgHeader(name, meetingId, userId)
|
||||
}
|
||||
|
||||
def makeEnvelope(msgType: String, name: String, meetingId: String, userId: String): BbbCoreEnvelope = {
|
||||
val routing = Routing.addMsgToClientRouting(msgType, meetingId, userId)
|
||||
BbbCoreEnvelope(name, routing)
|
||||
}
|
||||
|
||||
def makeBody(
|
||||
chatId: String,
|
||||
access: String, correlationId: String,
|
||||
createdBy: GroupChatUser, users: Vector[GroupChatUser],
|
||||
msgs: Vector[GroupChatMsgToUser]
|
||||
): GroupChatCreatedEvtMsgBody = {
|
||||
GroupChatCreatedEvtMsgBody(correlationId, chatId, createdBy,
|
||||
access, users, msgs)
|
||||
}
|
||||
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
val correlationId = msg.body.correlationId
|
||||
val users = gc.users
|
||||
val msgs = gc.msgs.map(m => GroupChatApp.toMessageToUser(m))
|
||||
|
||||
if (gc.access == GroupChatAccess.PRIVATE) {
|
||||
def sendDirectMessage(userId: String): Unit = {
|
||||
val envelope = makeEnvelope(MessageTypes.DIRECT, GroupChatCreatedEvtMsg.NAME, meetingId, userId)
|
||||
val header = makeHeader(GroupChatCreatedEvtMsg.NAME, meetingId, userId)
|
||||
|
||||
val body = makeBody(gc.id, gc.access, correlationId, gc.createdBy, users, msgs)
|
||||
val event = GroupChatCreatedEvtMsg(header, body)
|
||||
val outEvent = BbbCommonEnvCoreMsg(envelope, event)
|
||||
bus.outGW.send(outEvent)
|
||||
}
|
||||
|
||||
users.foreach(u => sendDirectMessage(u.id))
|
||||
|
||||
} else {
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
val userId = msg.header.userId
|
||||
val envelope = makeEnvelope(MessageTypes.BROADCAST_TO_MEETING, GroupChatCreatedEvtMsg.NAME,
|
||||
meetingId, userId)
|
||||
val header = makeHeader(GroupChatCreatedEvtMsg.NAME, meetingId, userId)
|
||||
|
||||
val body = makeBody(gc.id, gc.access, correlationId, gc.createdBy, users, msgs)
|
||||
val event = GroupChatCreatedEvtMsg(header, body)
|
||||
|
||||
val outEvent = BbbCommonEnvCoreMsg(envelope, event)
|
||||
bus.outGW.send(outEvent)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package org.bigbluebutton.core.apps.groupchats
|
||||
|
||||
trait DestroyGroupChatReqMsgHdlr {
|
||||
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
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 GetGroupChatMsgsReqMsgHdlr {
|
||||
def handle(msg: GetGroupChatMsgsReqMsg, state: MeetingState2x,
|
||||
liveMeeting: LiveMeeting, bus: MessageBus): MeetingState2x = {
|
||||
|
||||
def buildGetGroupChatMsgsRespMsg(meetingId: String, userId: String,
|
||||
msgs: Vector[GroupChatMsgToUser], chatId: String): BbbCommonEnvCoreMsg = {
|
||||
val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, meetingId, userId)
|
||||
val envelope = BbbCoreEnvelope(GetGroupChatMsgsRespMsg.NAME, routing)
|
||||
val header = BbbClientMsgHeader(GetGroupChatMsgsRespMsg.NAME, meetingId, userId)
|
||||
|
||||
val body = GetGroupChatMsgsRespMsgBody(chatId, msgs)
|
||||
val event = GetGroupChatMsgsRespMsg(header, body)
|
||||
|
||||
BbbCommonEnvCoreMsg(envelope, event)
|
||||
}
|
||||
|
||||
state.groupChats.find(msg.body.chatId) foreach { gc =>
|
||||
if (gc.access == GroupChatAccess.PUBLIC || gc.isUserMemberOf(msg.header.userId)) {
|
||||
val msgs = gc.msgs.toVector map (m => GroupChatMsgToUser(m.id, m.createdOn, m.correlationId,
|
||||
m.sender, m.chatEmphasizedText, m.message))
|
||||
val respMsg = buildGetGroupChatMsgsRespMsg(
|
||||
liveMeeting.props.meetingProp.intId,
|
||||
msg.header.userId, msgs, gc.id
|
||||
)
|
||||
bus.outGW.send(respMsg)
|
||||
}
|
||||
}
|
||||
|
||||
state
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
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 GetGroupChatsReqMsgHdlr {
|
||||
this: GroupChatHdlrs =>
|
||||
|
||||
def handle(msg: GetGroupChatsReqMsg, state: MeetingState2x,
|
||||
liveMeeting: LiveMeeting, bus: MessageBus): MeetingState2x = {
|
||||
|
||||
log.debug("RECEIVED GetGroupChatsReqMsg")
|
||||
|
||||
def buildGetGroupChatsRespMsg(meetingId: String, userId: String,
|
||||
allChats: Vector[GroupChatInfo]): BbbCommonEnvCoreMsg = {
|
||||
val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, meetingId, userId)
|
||||
val envelope = BbbCoreEnvelope(GetGroupChatsRespMsg.NAME, routing)
|
||||
val header = BbbClientMsgHeader(GetGroupChatsRespMsg.NAME, meetingId, userId)
|
||||
|
||||
val body = GetGroupChatsRespMsgBody(allChats)
|
||||
val event = GetGroupChatsRespMsg(header, body)
|
||||
|
||||
BbbCommonEnvCoreMsg(envelope, event)
|
||||
}
|
||||
|
||||
val publicChats = state.groupChats.findAllPublicChats()
|
||||
val privateChats = state.groupChats.findAllPrivateChatsForUser(msg.header.userId)
|
||||
val pubChats = publicChats map (pc => GroupChatInfo(pc.id, pc.access, pc.createdBy, pc.users))
|
||||
val privChats = privateChats map (pc => GroupChatInfo(pc.id, pc.access, pc.createdBy, pc.users))
|
||||
|
||||
val allChats = pubChats ++ privChats
|
||||
|
||||
val respMsg = buildGetGroupChatsRespMsg(liveMeeting.props.meetingProp.intId, msg.header.userId, allChats)
|
||||
|
||||
bus.outGW.send(respMsg)
|
||||
|
||||
state
|
||||
}
|
||||
}
|
@ -0,0 +1,93 @@
|
||||
package org.bigbluebutton.core.apps.groupchats
|
||||
|
||||
import org.bigbluebutton.common2.msgs.{ GroupChatAccess, GroupChatMsgFromUser, GroupChatMsgToUser, GroupChatUser }
|
||||
import org.bigbluebutton.core.domain.MeetingState2x
|
||||
import org.bigbluebutton.core.models._
|
||||
import org.bigbluebutton.core.running.LiveMeeting
|
||||
|
||||
object GroupChatApp {
|
||||
|
||||
val MAIN_PUBLIC_CHAT = "MAIN-PUBLIC-GROUP-CHAT"
|
||||
|
||||
def createGroupChat(access: String, createBy: GroupChatUser,
|
||||
users: Vector[GroupChatUser], msgs: Vector[GroupChatMessage]): GroupChat = {
|
||||
val gcId = GroupChatFactory.genId()
|
||||
GroupChatFactory.create(gcId, access, createBy, users, msgs)
|
||||
}
|
||||
|
||||
def toGroupChatMessage(sender: GroupChatUser, msg: GroupChatMsgFromUser): GroupChatMessage = {
|
||||
val now = System.currentTimeMillis()
|
||||
val id = GroupChatFactory.genId()
|
||||
GroupChatMessage(id, now, msg.correlationId, now, now, sender, msg.chatEmphasizedText, msg.message)
|
||||
}
|
||||
|
||||
def toMessageToUser(msg: GroupChatMessage): GroupChatMsgToUser = {
|
||||
GroupChatMsgToUser(id = msg.id, timestamp = msg.timestamp, correlationId = msg.correlationId,
|
||||
sender = msg.sender, chatEmphasizedText = msg.chatEmphasizedText, message = msg.message)
|
||||
}
|
||||
|
||||
def addGroupChatMessage(chat: GroupChat, chats: GroupChats,
|
||||
msg: GroupChatMessage): GroupChats = {
|
||||
val c = chat.add(msg)
|
||||
chats.update(c)
|
||||
}
|
||||
|
||||
def findGroupChatUser(userId: String, users: Users2x): Option[GroupChatUser] = {
|
||||
Users2x.findWithIntId(users, userId) match {
|
||||
case Some(u) => Some(GroupChatUser(u.intId, u.name, u.role))
|
||||
case None =>
|
||||
if (userId == SystemUser.ID) {
|
||||
Some(GroupChatUser(SystemUser.ID))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def createDefaultPublicGroupChat(): GroupChat = {
|
||||
val createBy = GroupChatUser(SystemUser.ID)
|
||||
GroupChatFactory.create(MAIN_PUBLIC_CHAT, GroupChatAccess.PUBLIC, createBy, Vector.empty, Vector.empty)
|
||||
}
|
||||
|
||||
def createTestPublicGroupChat(state: MeetingState2x): MeetingState2x = {
|
||||
val createBy = GroupChatUser(SystemUser.ID)
|
||||
val defaultPubGroupChat = GroupChatFactory.create(
|
||||
"TEST_GROUP_CHAT",
|
||||
GroupChatAccess.PUBLIC, createBy, Vector.empty, Vector.empty
|
||||
)
|
||||
val groupChats = state.groupChats.add(defaultPubGroupChat)
|
||||
state.update(groupChats)
|
||||
}
|
||||
|
||||
def getAllGroupChatsInMeeting(state: MeetingState2x): Vector[GroupChat] = {
|
||||
state.groupChats.getAllGroupChatsInMeeting()
|
||||
}
|
||||
|
||||
def genTestChatMsgHistory(chatId: String, state: MeetingState2x, userId: String, liveMeeting: LiveMeeting): MeetingState2x = {
|
||||
def addH(state: MeetingState2x, userId: String, liveMeeting: LiveMeeting, msg: GroupChatMsgFromUser): MeetingState2x = {
|
||||
val newState = for {
|
||||
sender <- GroupChatApp.findGroupChatUser(userId, liveMeeting.users2x)
|
||||
chat <- state.groupChats.find(chatId)
|
||||
} yield {
|
||||
|
||||
val gcm1 = GroupChatApp.toGroupChatMessage(sender, msg)
|
||||
val gcs1 = GroupChatApp.addGroupChatMessage(chat, state.groupChats, gcm1)
|
||||
state.update(gcs1)
|
||||
}
|
||||
|
||||
newState match {
|
||||
case Some(ns) => ns
|
||||
case None => state
|
||||
}
|
||||
}
|
||||
|
||||
val sender = GroupChatUser(SystemUser.ID)
|
||||
val h1 = GroupChatMsgFromUser(correlationId = "cor1", sender = sender, message = "Hello Foo!")
|
||||
val h2 = GroupChatMsgFromUser(correlationId = "cor2", sender = sender, message = "Hello Bar!")
|
||||
val h3 = GroupChatMsgFromUser(correlationId = "cor3", sender = sender, message = "Hello Baz!")
|
||||
val state1 = addH(state, SystemUser.ID, liveMeeting, h1)
|
||||
val state2 = addH(state1, SystemUser.ID, liveMeeting, h2)
|
||||
val state3 = addH(state2, SystemUser.ID, liveMeeting, h3)
|
||||
state3
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package org.bigbluebutton.core.apps.groupchats
|
||||
|
||||
trait GroupChatAddUserReqMsgHdlr {
|
||||
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package org.bigbluebutton.core.apps.groupchats
|
||||
|
||||
import akka.actor.ActorContext
|
||||
import akka.event.Logging
|
||||
|
||||
class GroupChatHdlrs(implicit val context: ActorContext)
|
||||
extends CreateGroupChatReqMsgHdlr
|
||||
with CreateDefaultPublicGroupChat
|
||||
with GetGroupChatMsgsReqMsgHdlr
|
||||
with GetGroupChatsReqMsgHdlr
|
||||
with SendGroupChatMessageMsgHdlr
|
||||
with SyncGetGroupChatsInfoMsgHdlr {
|
||||
|
||||
val log = Logging(context.system, getClass)
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package org.bigbluebutton.core.apps.groupchats
|
||||
|
||||
trait GroupChatRemoveUserReqMsgHdlr {
|
||||
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package org.bigbluebutton.core.apps.groupchats
|
||||
|
||||
import org.bigbluebutton.common2.msgs.{ OpenGroupChatWindowReqMsg }
|
||||
import org.bigbluebutton.core.bus.MessageBus
|
||||
import org.bigbluebutton.core.domain.MeetingState2x
|
||||
import org.bigbluebutton.core.running.LiveMeeting
|
||||
|
||||
trait OpenGroupChatWindowReqMsgHdlr {
|
||||
this: GroupChatHdlrs =>
|
||||
|
||||
def handle(msg: OpenGroupChatWindowReqMsg, state: MeetingState2x,
|
||||
liveMeeting: LiveMeeting, bus: MessageBus): MeetingState2x = {
|
||||
state
|
||||
}
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
package org.bigbluebutton.core.apps.groupchats
|
||||
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.apps.PermissionCheck
|
||||
import org.bigbluebutton.core.bus.MessageBus
|
||||
import org.bigbluebutton.core.domain.MeetingState2x
|
||||
import org.bigbluebutton.core.running.{ HandlerHelpers, LiveMeeting }
|
||||
import org.bigbluebutton.core.models.Users2x
|
||||
import org.bigbluebutton.core2.MeetingStatus2x
|
||||
import org.bigbluebutton.core.models.Roles
|
||||
|
||||
trait SendGroupChatMessageMsgHdlr extends HandlerHelpers {
|
||||
this: GroupChatHdlrs =>
|
||||
|
||||
def handle(msg: SendGroupChatMessageMsg, state: MeetingState2x,
|
||||
liveMeeting: LiveMeeting, bus: MessageBus): MeetingState2x = {
|
||||
|
||||
val chatDisabled: Boolean = liveMeeting.props.meetingProp.disabledFeatures.contains("chat")
|
||||
var chatLocked: Boolean = false
|
||||
|
||||
for {
|
||||
user <- Users2x.findWithIntId(liveMeeting.users2x, msg.header.userId)
|
||||
groupChat <- state.groupChats.find(msg.body.chatId)
|
||||
} yield {
|
||||
if (user.role != Roles.MODERATOR_ROLE && user.locked) {
|
||||
val permissions = MeetingStatus2x.getPermissions(liveMeeting.status)
|
||||
if (groupChat.access == GroupChatAccess.PRIVATE) {
|
||||
val modMembers = groupChat.users.filter(cu => Users2x.findWithIntId(liveMeeting.users2x, cu.id) match {
|
||||
case Some(user) => user.role == Roles.MODERATOR_ROLE
|
||||
case None => false
|
||||
})
|
||||
// don't lock private chats that involve a moderator
|
||||
if (modMembers.length == 0) {
|
||||
chatLocked = permissions.disablePrivChat
|
||||
}
|
||||
} else {
|
||||
chatLocked = permissions.disablePubChat
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!chatDisabled && !(applyPermissionCheck && chatLocked)) {
|
||||
val newState = for {
|
||||
sender <- GroupChatApp.findGroupChatUser(msg.header.userId, liveMeeting.users2x)
|
||||
chat <- state.groupChats.find(msg.body.chatId)
|
||||
} yield {
|
||||
val chatIsPrivate = chat.access == GroupChatAccess.PRIVATE;
|
||||
val userIsAParticipant = chat.users.filter(u => u.id == sender.id).length > 0;
|
||||
|
||||
if ((chatIsPrivate && userIsAParticipant) || !chatIsPrivate) {
|
||||
val gcm = GroupChatApp.toGroupChatMessage(sender, msg.body.msg)
|
||||
val gcs = GroupChatApp.addGroupChatMessage(chat, state.groupChats, gcm)
|
||||
|
||||
val event = buildGroupChatMessageBroadcastEvtMsg(
|
||||
liveMeeting.props.meetingProp.intId,
|
||||
msg.header.userId, msg.body.chatId, gcm
|
||||
)
|
||||
|
||||
bus.outGW.send(event)
|
||||
|
||||
state.update(gcs)
|
||||
} else {
|
||||
val reason = "User isn't a participant of the chat"
|
||||
PermissionCheck.ejectUserForFailedPermission(msg.header.meetingId, msg.header.userId, reason, bus.outGW, liveMeeting)
|
||||
state
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
newState match {
|
||||
case Some(ns) => ns
|
||||
case None => state
|
||||
}
|
||||
} else { state }
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
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.addMsgToHtml5InstanceIdRouting(liveMeeting.props.meetingProp.intId, liveMeeting.props.systemProps.html5InstanceId.toString)
|
||||
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.addMsgToHtml5InstanceIdRouting(liveMeeting.props.meetingProp.intId, liveMeeting.props.systemProps.html5InstanceId.toString)
|
||||
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
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
package org.bigbluebutton.core.apps.layout
|
||||
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.models.{ Layouts, LayoutsType }
|
||||
import org.bigbluebutton.core.running.OutMsgRouter
|
||||
import org.bigbluebutton.core2.MeetingStatus2x
|
||||
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
|
||||
|
||||
trait BroadcastLayoutMsgHdlr extends RightsManagementTrait {
|
||||
this: LayoutApp2x =>
|
||||
|
||||
val outGW: OutMsgRouter
|
||||
|
||||
def handleBroadcastLayoutMsg(msg: BroadcastLayoutMsg): Unit = {
|
||||
|
||||
if (liveMeeting.props.meetingProp.disabledFeatures.contains("layouts")) {
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
val reason = "Layouts is disabled for this meeting."
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, outGW, liveMeeting)
|
||||
} 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 broadcast layout to meeting."
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, outGW, liveMeeting)
|
||||
} else {
|
||||
if (LayoutsType.layoutsType.contains(msg.body.layout)) {
|
||||
val newlayout = LayoutsType.layoutsType.getOrElse(msg.body.layout, "")
|
||||
|
||||
Layouts.setCurrentLayout(liveMeeting.layouts, newlayout)
|
||||
Layouts.setPushLayout(liveMeeting.layouts, msg.body.pushLayout)
|
||||
Layouts.setPresentationIsOpen(liveMeeting.layouts, msg.body.presentationIsOpen)
|
||||
Layouts.setCameraDockIsResizing(liveMeeting.layouts, msg.body.isResizing)
|
||||
Layouts.setCameraPosition(liveMeeting.layouts, msg.body.cameraPosition)
|
||||
Layouts.setFocusedCamera(liveMeeting.layouts, msg.body.focusedCamera)
|
||||
Layouts.setPresentationVideoRate(liveMeeting.layouts, msg.body.presentationVideoRate)
|
||||
Layouts.setRequestedBy(liveMeeting.layouts, msg.header.userId)
|
||||
|
||||
sendBroadcastLayoutEvtMsg(msg.header.userId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def sendBroadcastLayoutEvtMsg(fromUserId: String): Unit = {
|
||||
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, liveMeeting.props.meetingProp.intId, fromUserId)
|
||||
val envelope = BbbCoreEnvelope(BroadcastLayoutEvtMsg.NAME, routing)
|
||||
val header = BbbClientMsgHeader(BroadcastLayoutEvtMsg.NAME, liveMeeting.props.meetingProp.intId, fromUserId)
|
||||
|
||||
val body = BroadcastLayoutEvtMsgBody(
|
||||
Layouts.getCurrentLayout(liveMeeting.layouts),
|
||||
Layouts.getPushLayout(liveMeeting.layouts),
|
||||
Layouts.getPresentationIsOpen(liveMeeting.layouts),
|
||||
Layouts.getCameraDockIsResizing(liveMeeting.layouts),
|
||||
Layouts.getCameraPosition(liveMeeting.layouts),
|
||||
Layouts.getFocusedCamera(liveMeeting.layouts),
|
||||
Layouts.getPresentationVideoRate(liveMeeting.layouts),
|
||||
MeetingStatus2x.getPermissions(liveMeeting.status).lockedLayout,
|
||||
Layouts.getLayoutSetter(liveMeeting.layouts)
|
||||
)
|
||||
val event = BroadcastLayoutEvtMsg(header, body)
|
||||
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
|
||||
|
||||
outGW.send(msgEvent)
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user