Merge branch 'develop' into fixjitterbuffer

This commit is contained in:
Fred Dixon 2022-12-30 07:28:55 -05:00 committed by GitHub
commit 4c359d83a7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
488 changed files with 15988 additions and 17624 deletions

View File

@ -13,7 +13,7 @@ jobs:
build-install-and-test:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- run: ./build/get_external_dependencies.sh
- run: ./build/setup.sh bbb-apps-akka
- run: ./build/setup.sh bbb-config

View File

@ -6,8 +6,13 @@ on:
- 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

View File

@ -11,7 +11,7 @@ stages:
# define which docker image to use for builds
default:
image: gitlab.senfcall.de:5050/senfcall-public/docker-bbb-build:v2022-07-12
image: gitlab.senfcall.de:5050/senfcall-public/docker-bbb-build:v2022-12-08-meteor-290
# 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
@ -99,7 +99,7 @@ bbb-html5-build:
script:
- build/setup-inside-docker.sh bbb-html5
bbb-learning-dashboard:
bbb-learning-dashboard-build:
extends: .build_job
script:
- build/setup-inside-docker.sh bbb-learning-dashboard

View File

@ -18,7 +18,7 @@ val compileSettings = Seq(
"-Xlint",
"-Ywarn-dead-code",
"-language:_",
"-target:jvm-1.11",
"-target:11",
"-encoding", "UTF-8"
),
javacOptions ++= List(
@ -48,7 +48,7 @@ lazy val bbbAppsAkka = (project in file(".")).settings(name := "bbb-apps-akka",
// Config file is in ./.scalariform.conf
scalariformAutoformat := true
scalaVersion := "2.13.4"
scalaVersion := "2.13.9"
//-----------
// Packaging
//

View File

@ -7,7 +7,7 @@ object Dependencies {
object Versions {
// Scala
val scala = "2.13.4"
val scala = "2.13.9"
val junit = "4.12"
val junitInterface = "0.11"
val scalactic = "3.0.8"
@ -26,7 +26,7 @@ object Dependencies {
val codec = "1.15"
// BigBlueButton
val bbbCommons = "0.0.21-SNAPSHOT"
val bbbCommons = "0.0.22-SNAPSHOT"
// Test
val scalaTest = "3.2.11"

View File

@ -74,7 +74,6 @@ class BigBlueButtonActor(
case m: CreateMeetingReqMsg => handleCreateMeetingReqMsg(m)
case m: RegisterUserReqMsg => handleRegisterUserReqMsg(m)
case m: EjectDuplicateUserReqMsg => handleEjectDuplicateUserReqMsg(m)
case m: GetAllMeetingsReqMsg => handleGetAllMeetingsReqMsg(m)
case m: GetRunningMeetingsReqMsg => handleGetRunningMeetingsReqMsg(m)
case m: CheckAlivePingSysMsg => handleCheckAlivePingSysMsg(m)
@ -105,16 +104,6 @@ class BigBlueButtonActor(
}
}
def handleEjectDuplicateUserReqMsg(msg: EjectDuplicateUserReqMsg): Unit = {
log.debug("RECEIVED EjectDuplicateUserReqMsg msg {}", msg)
for {
m <- RunningMeetings.findWithId(meetings, msg.header.meetingId)
} yield {
log.debug("FORWARDING EjectDuplicateUserReqMsg")
m.actorRef forward (msg)
}
}
def handleCreateMeetingReqMsg(msg: CreateMeetingReqMsg): Unit = {
log.debug("RECEIVED CreateMeetingReqMsg msg {}", msg)

View File

@ -104,6 +104,21 @@ case class SendMessageToBreakoutRoomInternalMsg(parentId: String, breakoutId: St
*/
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

View File

@ -13,9 +13,11 @@ object BreakoutModel {
isDefaultName: Boolean,
freeJoin: Boolean,
voiceConf: String,
assignedUsers: Vector[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)
new BreakoutRoom2x(id, externalId, name, parentId, sequence, shortName, isDefaultName, freeJoin, voiceConf, assignedUsers, Vector(), Vector(), None, false, captureNotes, captureSlides)
}
}

View File

@ -1,9 +1,6 @@
package org.bigbluebutton.core.apps
import org.bigbluebutton.core.util.jhotdraw.BezierWrapper
import scala.collection.immutable.List
import scala.collection.immutable.HashMap
import scala.collection.JavaConverters._
import org.bigbluebutton.common2.msgs.AnnotationVO
import org.bigbluebutton.core.apps.whiteboard.Whiteboard
import org.bigbluebutton.SystemConfiguration
@ -24,86 +21,83 @@ class WhiteboardModel extends SystemConfiguration {
}
private def createWhiteboard(wbId: String): Whiteboard = {
new Whiteboard(
Whiteboard(
wbId,
Array.empty[String],
Array.empty[String],
System.currentTimeMillis(),
new HashMap[String, Map[String, AnnotationVO]]()
new HashMap[String, AnnotationVO]
)
}
private def getAnnotationsByUserId(wb: Whiteboard, id: String): Map[String, AnnotationVO] = {
wb.annotationsMap.get(id).getOrElse(Map[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]): Array[AnnotationVO] = {
def addAnnotations(wbId: String, userId: String, annotations: Array[AnnotationVO], isPresenter: Boolean, isModerator: Boolean): Array[AnnotationVO] = {
var annotationsAdded = Array[AnnotationVO]()
val wb = getWhiteboard(wbId)
val usersAnnotations = getAnnotationsByUserId(wb, userId)
var newUserAnnotations = usersAnnotations
var newAnnotationsMap = wb.annotationsMap
for (annotation <- annotations) {
newUserAnnotations = newUserAnnotations + (annotation.id -> annotation)
println("Adding annotation to page [" + wb.id + "]. After numAnnotations=[" + newUserAnnotations.size + "].")
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 newAnnotationsMap = wb.annotationsMap + (userId -> newUserAnnotations)
val newWb = wb.copy(annotationsMap = newAnnotationsMap)
saveWhiteboard(newWb)
annotations
annotationsAdded
}
def getHistory(wbId: String): Array[AnnotationVO] = {
//wb.annotationsMap.values.flatten.toArray.sortBy(_.position);
val wb = getWhiteboard(wbId)
var annotations = Array[AnnotationVO]()
// TODO: revisit this, probably there is a one-liner simple solution
wb.annotationsMap.values.foreach(
user => user.values.foreach(
annotation => annotations = annotations :+ annotation
)
)
annotations
wb.annotationsMap.values.toArray
}
def clearWhiteboard(wbId: String, userId: String): Option[Boolean] = {
var cleared: Option[Boolean] = None
if (hasWhiteboard(wbId)) {
val wb = getWhiteboard(wbId)
if (wb.multiUser.contains(userId)) {
if (wb.annotationsMap.contains(userId)) {
val newWb = wb.copy(annotationsMap = wb.annotationsMap - userId)
saveWhiteboard(newWb)
cleared = Some(false)
}
} else {
if (wb.annotationsMap.nonEmpty) {
val newWb = wb.copy(annotationsMap = new HashMap[String, Map[String, AnnotationVO]]())
saveWhiteboard(newWb)
cleared = Some(true)
}
}
}
cleared
}
def deleteAnnotations(wbId: String, userId: String, annotationsIds: Array[String]): Array[String] = {
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
val usersAnnotations = getAnnotationsByUserId(wb, userId)
var newUserAnnotations = usersAnnotations
for (annotationId <- annotationsIds) {
val annotation = usersAnnotations.get(annotationId)
val annotation = wb.annotationsMap.get(annotationId)
//not empty and annotation exists
if (!usersAnnotations.isEmpty && !annotation.isEmpty) {
newUserAnnotations = newUserAnnotations - annotationId
println("Removing annotation on page [" + wb.id + "]. After numAnnotations=[" + newUserAnnotations.size + "].")
annotationsIdsRemoved = annotationsIdsRemoved :+ 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 newAnnotationsMap = wb.annotationsMap + (userId -> newUserAnnotations)
val newWb = wb.copy(annotationsMap = newAnnotationsMap)
saveWhiteboard(newWb)
annotationsIdsRemoved

View File

@ -52,7 +52,7 @@ trait BreakoutRoomCreatedMsgHdlr {
(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)
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)
@ -79,7 +79,7 @@ trait BreakoutRoomCreatedMsgHdlr {
BbbCommonEnvCoreMsg(envelope, event)
}
val breakoutInfo = BreakoutRoomInfo(room.name, room.externalId, room.id, room.sequence, room.shortName, room.isDefaultName, room.freeJoin, Map())
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)

View File

@ -28,7 +28,7 @@ trait BreakoutRoomsListMsgHdlr {
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())
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)

View File

@ -52,7 +52,7 @@ trait CreateBreakoutRoomsCmdMsgHdlr extends RightsManagementTrait {
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)
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)
}
@ -70,7 +70,9 @@ trait CreateBreakoutRoomsCmdMsgHdlr extends RightsManagementTrait {
liveMeeting.props.password.moderatorPass,
liveMeeting.props.password.viewerPass,
presId, presSlide, msg.body.record,
liveMeeting.props.breakoutProps.privateChatEnabled
liveMeeting.props.breakoutProps.privateChatEnabled,
breakout.captureNotes,
breakout.captureSlides,
)
val event = buildCreateBreakoutRoomSysCmdMsg(liveMeeting.props.meetingProp.intId, roomDetail)

View File

@ -14,8 +14,8 @@ trait EndAllBreakoutRoomsMsgHdlr extends RightsManagementTrait {
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 meetingId = liveMeeting.props.meetingProp.intId
val reason = "No permission to end breakout rooms for meeting."
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, outGW, liveMeeting)
state
@ -24,11 +24,11 @@ trait EndAllBreakoutRoomsMsgHdlr extends RightsManagementTrait {
model <- state.breakout
} yield {
model.rooms.values.foreach { room =>
eventBus.publish(BigBlueButtonEvent(room.id, EndBreakoutRoomInternalMsg(props.breakoutProps.parentId, room.id, MeetingEndReason.BREAKOUT_ENDED_BY_MOD)))
eventBus.publish(BigBlueButtonEvent(room.id, EndBreakoutRoomInternalMsg(meetingId, room.id, MeetingEndReason.BREAKOUT_ENDED_BY_MOD)))
}
val notifyEvent = MsgBuilder.buildNotifyAllInMeetingEvtMsg(
liveMeeting.props.meetingProp.intId,
meetingId,
"info",
"rooms",
"app.toast.breakoutRoomEnded",

View File

@ -1,7 +1,7 @@
package org.bigbluebutton.core.apps.breakout
import org.bigbluebutton.core.api.EndBreakoutRoomInternalMsg
import org.bigbluebutton.core.bus.{ InternalEventBus }
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 {
@ -12,6 +12,18 @@ trait EndBreakoutRoomInternalMsgHdlr extends HandlerHelpers {
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")
}

View File

@ -84,10 +84,12 @@ trait CreateGroupChatReqMsgHdlr extends SystemConfiguration {
BbbCoreEnvelope(name, routing)
}
def makeBody(chatId: String,
access: String, correlationId: String,
createdBy: GroupChatUser, users: Vector[GroupChatUser],
msgs: Vector[GroupChatMsgToUser]): GroupChatCreatedEvtMsgBody = {
def makeBody(
chatId: String,
access: String, correlationId: String,
createdBy: GroupChatUser, users: Vector[GroupChatUser],
msgs: Vector[GroupChatMsgToUser]
): GroupChatCreatedEvtMsgBody = {
GroupChatCreatedEvtMsgBody(correlationId, chatId, createdBy,
access, users, msgs)
}

View File

@ -0,0 +1,38 @@
package org.bigbluebutton.core.apps.pads
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
import org.bigbluebutton.core.bus.MessageBus
import org.bigbluebutton.core.models.Pads
import org.bigbluebutton.core.running.LiveMeeting
trait PadPinnedReqMsgHdlr extends RightsManagementTrait {
this: PadsApp2x =>
def handle(msg: PadPinnedReqMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
def broadcastEvent(groupId: String, pinned: Boolean): Unit = {
val routing = collection.immutable.HashMap("sender" -> "bbb-apps-akka")
val envelope = BbbCoreEnvelope(PadPinnedEvtMsg.NAME, routing)
val header = BbbClientMsgHeader(PadPinnedEvtMsg.NAME, liveMeeting.props.meetingProp.intId, "not-used")
val body = PadPinnedEvtMsgBody(groupId, pinned)
val event = PadPinnedEvtMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
bus.outGW.send(msgEvent)
}
if (Pads.hasAccess(liveMeeting, msg.body.externalId, msg.header.userId)) {
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 pin Shared Notes"
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting)
} else {
Pads.getGroup(liveMeeting.pads, msg.body.externalId) match {
case Some(group) => broadcastEvent(group.externalId, msg.body.pinned)
case _ =>
}
}
}
}
}

View File

@ -13,6 +13,7 @@ class PadsApp2x(implicit val context: ActorContext)
with PadUpdatedSysMsgHdlr
with PadContentSysMsgHdlr
with PadPatchSysMsgHdlr
with PadUpdatePubMsgHdlr {
with PadUpdatePubMsgHdlr
with PadPinnedReqMsgHdlr {
}

View File

@ -45,27 +45,31 @@ trait RespondToPollReqMsgHdlr {
bus.outGW.send(msgEvent)
}
for {
(pollId: String, updatedPoll: SimplePollResultOutVO) <- Polls.handleRespondToPollReqMsg(msg.header.userId, msg.body.pollId,
msg.body.questionId, msg.body.answerIds, liveMeeting)
} yield {
broadcastPollUpdatedEvent(msg, pollId, updatedPoll)
if (Polls.checkUserResponded(msg.body.pollId, msg.header.userId, liveMeeting.polls) == false) {
for {
poll <- Polls.getPoll(pollId, liveMeeting.polls)
(pollId: String, updatedPoll: SimplePollResultOutVO) <- Polls.handleRespondToPollReqMsg(msg.header.userId, msg.body.pollId,
msg.body.questionId, msg.body.answerIds, liveMeeting)
} yield {
broadcastPollUpdatedEvent(msg, pollId, updatedPoll)
for {
answerId <- msg.body.answerIds
poll <- Polls.getPoll(pollId, liveMeeting.polls)
} yield {
val answerText = poll.questions(0).answers.get(answerId).key
broadcastUserRespondedToPollRecordMsg(msg, pollId, answerId, answerText, poll.isSecret)
for {
answerId <- msg.body.answerIds
} yield {
val answerText = poll.questions(0).answers.get(answerId).key
broadcastUserRespondedToPollRecordMsg(msg, pollId, answerId, answerText, poll.isSecret)
}
}
for {
presenter <- Users2x.findPresenter(liveMeeting.users2x)
} yield {
broadcastUserRespondedToPollRespMsg(msg, pollId, msg.body.answerIds, presenter.intId)
}
}
for {
presenter <- Users2x.findPresenter(liveMeeting.users2x)
} yield {
broadcastUserRespondedToPollRespMsg(msg, pollId, msg.body.answerIds, presenter.intId)
}
} else {
log.info("Ignoring typed answer from user {} once user already added an answer to this poll {} in meeting {}", msg.header.userId, msg.body.pollId, msg.header.meetingId)
}
}
}

View File

@ -34,17 +34,23 @@ trait RespondToTypedPollReqMsgHdlr {
bus.outGW.send(msgEvent)
}
for {
(pollId: String, updatedPoll: SimplePollResultOutVO) <- Polls.handleRespondToTypedPollReqMsg(msg.header.userId, msg.body.pollId,
msg.body.questionId, msg.body.answer, liveMeeting)
} yield {
broadcastPollUpdatedEvent(msg, pollId, updatedPoll)
if (Polls.isResponsePollType(msg.body.pollId, liveMeeting.polls) &&
Polls.checkUserResponded(msg.body.pollId, msg.header.userId, liveMeeting.polls) == false &&
Polls.checkUserAddedQuestion(msg.body.pollId, msg.header.userId, liveMeeting.polls) == false) {
for {
presenter <- Users2x.findPresenter(liveMeeting.users2x)
(pollId: String, updatedPoll: SimplePollResultOutVO) <- Polls.handleRespondToTypedPollReqMsg(msg.header.userId, msg.body.pollId,
msg.body.questionId, msg.body.answer, liveMeeting)
} yield {
broadcastUserRespondedToTypedPollRespMsg(msg, pollId, msg.body.answer, presenter.intId)
broadcastPollUpdatedEvent(msg, pollId, updatedPoll)
for {
presenter <- Users2x.findPresenter(liveMeeting.users2x)
} yield {
broadcastUserRespondedToTypedPollRespMsg(msg, pollId, msg.body.answer, presenter.intId)
}
}
} else {
log.info("Ignoring typed answer from user {} once user already added an answer to this poll {} in meeting {}", msg.header.userId, msg.body.pollId, msg.header.meetingId)
}
}
}

View File

@ -20,7 +20,7 @@ trait PreuploadedPresentationsPubMsgHdlr {
val pages = new collection.mutable.HashMap[String, PageVO]()
pres.pages.foreach { p =>
val page = new PageVO(p.id, p.num, p.thumbUri, p.swfUri, p.txtUri, p.svgUri, p.current, p.xOffset, p.yOffset,
val page = new PageVO(p.id, p.num, p.thumbUri, p.txtUri, p.svgUri, p.current, p.xOffset, p.yOffset,
p.widthRatio, p.heightRatio)
pages += page.id -> page
}

View File

@ -27,7 +27,8 @@ trait PresentationConversionUpdatePubMsgHdlr {
msg.body.messageKey,
msg.body.code,
msg.body.presentationId,
msg.body.presName
msg.body.presName,
msg.body.temporaryPresentationId
)
val event = PresentationConversionUpdateEvtMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)

View File

@ -0,0 +1,38 @@
package org.bigbluebutton.core.apps.presentationpod
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.bus.MessageBus
import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.running.LiveMeeting
trait PresentationHasInvalidMimeTypeErrorPubMsgHdlr {
this: PresentationPodHdlrs =>
def handle(
msg: PresentationHasInvalidMimeTypeErrorSysPubMsg, state: MeetingState2x,
liveMeeting: LiveMeeting, bus: MessageBus
): MeetingState2x = {
def broadcastEvent(msg: PresentationHasInvalidMimeTypeErrorSysPubMsg): Unit = {
val routing = Routing.addMsgToClientRouting(
MessageTypes.BROADCAST_TO_MEETING,
liveMeeting.props.meetingProp.intId, msg.header.userId
)
val envelope = BbbCoreEnvelope(PresentationHasInvalidMimeTypeErrorEvtMsg.NAME, routing)
val header = BbbClientMsgHeader(
PresentationHasInvalidMimeTypeErrorEvtMsg.NAME,
liveMeeting.props.meetingProp.intId, msg.header.userId
)
val body = PresentationHasInvalidMimeTypeErrorEvtMsgBody(msg.body.podId, msg.body.meetingId,
msg.body.presentationName, msg.body.temporaryPresentationId,
msg.body.presentationId, msg.body.messageKey, msg.body.fileMime, msg.body.fileExtension)
val event = PresentationHasInvalidMimeTypeErrorEvtMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
bus.outGW.send(msgEvent)
}
broadcastEvent(msg)
state
}
}

View File

@ -25,7 +25,9 @@ class PresentationPodHdlrs(implicit val context: ActorContext)
with RemovePresentationPodPubMsgHdlr
with PresentationPageConvertedSysMsgHdlr
with PresentationPageConversionStartedSysMsgHdlr
with PresentationConversionEndedSysMsgHdlr {
with PresentationConversionEndedSysMsgHdlr
with PresentationUploadedFileTimeoutErrorPubMsgHdlr
with PresentationHasInvalidMimeTypeErrorPubMsgHdlr {
val log = Logging(context.system, getClass)
}

View File

@ -47,7 +47,6 @@ object PresentationPodsApp {
id = page.id,
num = page.num,
thumbUri = page.urls.getOrElse("thumb", ""),
swfUri = page.urls.getOrElse("swf", ""),
txtUri = page.urls.getOrElse("text", ""),
svgUri = page.urls.getOrElse("svg", ""),
current = page.current,
@ -80,7 +79,6 @@ object PresentationPodsApp {
id = page.id,
num = page.num,
thumbUri = page.urls.getOrElse("thumb", ""),
swfUri = page.urls.getOrElse("swf", ""),
txtUri = page.urls.getOrElse("text", ""),
svgUri = page.urls.getOrElse("svg", ""),
current = page.current,

View File

@ -0,0 +1,38 @@
package org.bigbluebutton.core.apps.presentationpod
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.bus.MessageBus
import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.running.LiveMeeting
trait PresentationUploadedFileTimeoutErrorPubMsgHdlr {
this: PresentationPodHdlrs =>
def handle(
msg: PresentationUploadedFileTimeoutErrorSysPubMsg, state: MeetingState2x,
liveMeeting: LiveMeeting, bus: MessageBus
): MeetingState2x = {
def broadcastEvent(msg: PresentationUploadedFileTimeoutErrorSysPubMsg): Unit = {
val routing = Routing.addMsgToClientRouting(
MessageTypes.BROADCAST_TO_MEETING,
liveMeeting.props.meetingProp.intId, msg.header.userId
)
val envelope = BbbCoreEnvelope(PresentationUploadedFileTooLargeErrorEvtMsg.NAME, routing)
val header = BbbClientMsgHeader(
PresentationUploadedFileTooLargeErrorEvtMsg.NAME,
liveMeeting.props.meetingProp.intId, msg.header.userId
)
val body = PresentationUploadedFileTimeoutErrorEvtMsgBody(msg.body.podId, msg.body.meetingId,
msg.body.presentationName, msg.body.page, msg.body.messageKey, msg.body.temporaryPresentationId,
msg.body.presentationId, msg.body.maxNumberOfAttempts)
val event = PresentationUploadedFileTimeoutErrorEvtMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
bus.outGW.send(msgEvent)
}
broadcastEvent(msg)
state
}
}

View File

@ -1,6 +1,7 @@
package org.bigbluebutton.core.apps.presentationpod
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.api.{ CapturePresentationReqInternalMsg, CaptureSharedNotesReqInternalMsg }
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
import org.bigbluebutton.core.bus.MessageBus
import org.bigbluebutton.core.domain.MeetingState2x
@ -12,6 +13,12 @@ import java.io.File
trait PresentationWithAnnotationsMsgHdlr extends RightsManagementTrait {
this: PresentationPodHdlrs =>
object JobTypes {
val DOWNLOAD = "PresentationWithAnnotationDownloadJob"
val CAPTURE_PRESENTATION = "PresentationWithAnnotationExportJob"
val CAPTURE_NOTES = "PadCaptureJob"
}
def buildStoreAnnotationsInRedisSysMsg(annotations: StoredAnnotations, liveMeeting: LiveMeeting): BbbCommonEnvCoreMsg = {
val routing = collection.immutable.HashMap("sender" -> "bbb-apps-akka")
val envelope = BbbCoreEnvelope(StoreAnnotationsInRedisSysMsg.NAME, routing)
@ -42,6 +49,16 @@ trait PresentationWithAnnotationsMsgHdlr extends RightsManagementTrait {
BbbCommonEnvCoreMsg(envelope, event)
}
def buildBroadcastPresAnnStatusMsg(presAnnStatusMsg: PresAnnStatusMsg, liveMeeting: LiveMeeting): BbbCommonEnvCoreMsg = {
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, liveMeeting.props.meetingProp.intId, "not-used")
val envelope = BbbCoreEnvelope(PresentationPageConvertedEventMsg.NAME, routing)
val header = BbbClientMsgHeader(PresAnnStatusEvtMsg.NAME, liveMeeting.props.meetingProp.intId, "not-used")
val body = PresAnnStatusEvtMsgBody(presId = presAnnStatusMsg.body.presId, pageNumber = presAnnStatusMsg.body.pageNumber, totalPages = presAnnStatusMsg.body.totalPages, status = presAnnStatusMsg.body.status, error = presAnnStatusMsg.body.error)
val event = PresAnnStatusEvtMsg(header, body)
BbbCommonEnvCoreMsg(envelope, event)
}
def buildPresentationUploadTokenSysPubMsg(parentId: String, userId: String, presentationUploadToken: String, filename: String): BbbCommonEnvCoreMsg = {
val routing = collection.immutable.HashMap("sender" -> "bbb-apps-akka")
val envelope = BbbCoreEnvelope(PresentationUploadTokenSysPubMsg.NAME, routing)
@ -100,7 +117,6 @@ trait PresentationWithAnnotationsMsgHdlr extends RightsManagementTrait {
log.error(s"Presentation ${presId} not found in meeting ${meetingId}")
} else {
val jobType: String = "PresentationWithAnnotationDownloadJob"
val jobId: String = RandomStringGenerator.randomAlphanumericString(16);
val allPages: Boolean = m.body.allPages
val pageCount = currentPres.get.pages.size
@ -109,7 +125,7 @@ trait PresentationWithAnnotationsMsgHdlr extends RightsManagementTrait {
val pages: List[Int] = m.body.pages // Desired presentation pages for export
val pagesRange: List[Int] = if (allPages) (1 to pageCount).toList else pages
val exportJob: ExportJob = new ExportJob(jobId, jobType, "annotated_slides", presId, presLocation, allPages, pagesRange, meetingId, "");
val exportJob: ExportJob = new ExportJob(jobId, JobTypes.DOWNLOAD, "annotated_slides", presId, presLocation, allPages, pagesRange, meetingId, "");
val storeAnnotationPages: List[PresentationPageForExport] = getPresentationPagesForExport(pagesRange, pageCount, presId, currentPres, liveMeeting);
// Send Export Job to Redis
@ -122,32 +138,26 @@ trait PresentationWithAnnotationsMsgHdlr extends RightsManagementTrait {
}
}
def handle(m: ExportPresentationWithAnnotationReqMsg, state: MeetingState2x, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
def handle(m: CapturePresentationReqInternalMsg, state: MeetingState2x, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
val meetingId = liveMeeting.props.meetingProp.intId
val userId = m.header.userId
val userId = m.userId
val presentationPods: Vector[PresentationPod] = state.presentationPodManager.getAllPresentationPodsInMeeting()
val currentPres: Option[PresentationInPod] = presentationPods.flatMap(_.getCurrentPresentation()).headOption
if (liveMeeting.props.meetingProp.disabledFeatures.contains("importPresentationWithAnnotationsFromBreakoutRooms")) {
val reason = "Importing slides from breakout rooms disabled for this meeting."
PermissionCheck.ejectUserForFailedPermission(meetingId, userId, reason, bus.outGW, liveMeeting)
} else if (permissionFailed(PermissionCheck.MOD_LEVEL, PermissionCheck.VIEWER_LEVEL, liveMeeting.users2x, userId)) {
val reason = "No permission to export presentation."
PermissionCheck.ejectUserForFailedPermission(meetingId, userId, reason, bus.outGW, liveMeeting)
log.error(s"Capturing breakout rooms slides disabled in meeting ${meetingId}.")
} else if (currentPres.isEmpty) {
log.error(s"No presentation set in meeting ${meetingId}")
} else {
val jobId: String = RandomStringGenerator.randomAlphanumericString(16);
val jobType = "PresentationWithAnnotationExportJob"
val allPages: Boolean = m.body.allPages
val allPages: Boolean = m.allPages
val pageCount = currentPres.get.pages.size
val presId: String = PresentationPodsApp.getAllPresentationPodsInMeeting(state).flatMap(_.getCurrentPresentation.map(_.id)).mkString
val presLocation = List("var", "bigbluebutton", meetingId, meetingId, presId).mkString(File.separator, File.separator, "");
val parentMeetingId: String = m.body.parentMeetingId
val parentMeetingId: String = m.parentMeetingId
val currentPage: PresentationPage = PresentationInPod.getCurrentPage(currentPres.get).get
val pagesRange: List[Int] = if (allPages) (1 to pageCount).toList else List(currentPage.num)
@ -166,7 +176,7 @@ trait PresentationWithAnnotationsMsgHdlr extends RightsManagementTrait {
// Informs bbb-web about the token so that when we use it to upload the presentation, it is able to look it up in the list of tokens
bus.outGW.send(buildPresentationUploadTokenSysPubMsg(parentMeetingId, userId, presentationUploadToken, filename))
val exportJob: ExportJob = new ExportJob(jobId, jobType, filename, presId, presLocation, allPages, pagesRange, parentMeetingId, presentationUploadToken)
val exportJob: ExportJob = new ExportJob(jobId, JobTypes.CAPTURE_PRESENTATION, filename, presId, presLocation, allPages, pagesRange, parentMeetingId, presentationUploadToken)
val storeAnnotationPages: List[PresentationPageForExport] = getPresentationPagesForExport(pagesRange, pageCount, presId, currentPres, liveMeeting);
// Send Export Job to Redis
@ -183,7 +193,35 @@ trait PresentationWithAnnotationsMsgHdlr extends RightsManagementTrait {
log.info("Received NewPresAnnFileAvailableMsg meetingId={} presId={} fileUrl={}", liveMeeting.props.meetingProp.intId, m.body.presId, m.body.fileURI)
bus.outGW.send(buildBroadcastNewPresAnnFileAvailable(m, liveMeeting))
}
def handle(m: CaptureSharedNotesReqInternalMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
val meetingId = liveMeeting.props.meetingProp.intId
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, meetingId, "not-used")
val envelope = BbbCoreEnvelope(PresentationPageConversionStartedEventMsg.NAME, routing)
val header = BbbClientMsgHeader(CaptureSharedNotesReqEvtMsg.NAME, meetingId, "not-used")
val body = CaptureSharedNotesReqEvtMsgBody(m.parentMeetingId, m.meetingName)
val event = CaptureSharedNotesReqEvtMsg(header, body)
bus.outGW.send(BbbCommonEnvCoreMsg(envelope, event))
}
def handle(m: PresAnnStatusMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
bus.outGW.send(buildBroadcastPresAnnStatusMsg(m, liveMeeting))
}
def handle(m: PadCapturePubMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
val userId: String = "system"
val jobId: String = s"${m.body.breakoutId}-notes" // Used as the temporaryPresentationId upon upload
val filename = m.body.filename
val presentationUploadToken: String = PresentationPodsApp.generateToken("DEFAULT_PRESENTATION_POD", userId)
bus.outGW.send(buildPresentationUploadTokenSysPubMsg(m.body.parentMeetingId, userId, presentationUploadToken, filename))
val exportJob = new ExportJob(jobId, JobTypes.CAPTURE_NOTES, filename, m.body.padId, "", true, List(), m.body.parentMeetingId, presentationUploadToken)
val job = buildStoreExportJobInRedisSysMsg(exportJob, liveMeeting)
bus.outGW.send(job)
}
}

View File

@ -42,6 +42,17 @@ trait ChangeLockSettingsInMeetingCmdMsgHdlr extends RightsManagementTrait {
MeetingStatus2x.setPermissions(liveMeeting.status, settings)
// Dial-in
def buildLockMessage(meetingId: String, userId: String, lockedBy: String, locked: Boolean): BbbCommonEnvCoreMsg = {
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, meetingId, userId)
val envelope = BbbCoreEnvelope(UserLockedInMeetingEvtMsg.NAME, routing)
val body = UserLockedInMeetingEvtMsgBody(userId, locked, lockedBy)
val header = BbbClientMsgHeader(UserLockedInMeetingEvtMsg.NAME, meetingId, userId)
val event = UserLockedInMeetingEvtMsg(header, body)
BbbCommonEnvCoreMsg(envelope, event)
}
if (oldPermissions.disableCam != settings.disableCam) {
if (settings.disableCam) {
val notifyEvent = MsgBuilder.buildNotifyAllInMeetingEvtMsg(
@ -55,24 +66,6 @@ trait ChangeLockSettingsInMeetingCmdMsgHdlr extends RightsManagementTrait {
outGW.send(notifyEvent)
LockSettingsUtil.enforceCamLockSettingsForAllUsers(liveMeeting, outGW)
// Dial-in
def buildLockMessage(meetingId: String, userId: String, lockedBy: String, locked: Boolean): BbbCommonEnvCoreMsg = {
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, meetingId, userId)
val envelope = BbbCoreEnvelope(UserLockedInMeetingEvtMsg.NAME, routing)
val body = UserLockedInMeetingEvtMsgBody(userId, locked, lockedBy)
val header = BbbClientMsgHeader(UserLockedInMeetingEvtMsg.NAME, meetingId, userId)
val event = UserLockedInMeetingEvtMsg(header, body)
BbbCommonEnvCoreMsg(envelope, event)
}
VoiceUsers.findAll(liveMeeting.voiceUsers) foreach { vu =>
if (vu.intId.startsWith(IntIdPrefixType.DIAL_IN)) { // only Dial-in users need this
val eventExplicitLock = buildLockMessage(liveMeeting.props.meetingProp.intId, vu.intId, msg.body.setBy, settings.disableMic)
outGW.send(eventExplicitLock)
}
}
} else {
val notifyEvent = MsgBuilder.buildNotifyAllInMeetingEvtMsg(
liveMeeting.props.meetingProp.intId,
@ -97,8 +90,12 @@ trait ChangeLockSettingsInMeetingCmdMsgHdlr extends RightsManagementTrait {
Vector()
)
outGW.send(notifyEvent)
// Apply lock settings when disableMic from false to true.
VoiceUsers.findAll(liveMeeting.voiceUsers) foreach { vu =>
if (vu.intId.startsWith(IntIdPrefixType.DIAL_IN)) { // only Dial-in users need this
val eventExplicitLock = buildLockMessage(liveMeeting.props.meetingProp.intId, vu.intId, msg.body.setBy, settings.disableMic)
outGW.send(eventExplicitLock)
}
}
LockSettingsUtil.enforceLockSettingsForAllVoiceUsers(liveMeeting, outGW)
} else {
val notifyEvent = MsgBuilder.buildNotifyAllInMeetingEvtMsg(

View File

@ -1,26 +0,0 @@
package org.bigbluebutton.core.apps.users
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.models.{ EjectReasonCode, SystemUser }
import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter }
import org.bigbluebutton.core2.message.senders.Sender
trait EjectDuplicateUserReqMsgHdlr {
this: UsersApp =>
val liveMeeting: LiveMeeting
val outGW: OutMsgRouter
def handleEjectDuplicateUserReqMsg(msg: EjectDuplicateUserReqMsg) {
val meetingId = liveMeeting.props.meetingProp.intId
val userId = msg.body.intUserId
val ejectedBy = SystemUser.ID
val reason = "user ejected because of duplicate external userid"
UsersApp.ejectUserFromMeeting(outGW, liveMeeting, userId, ejectedBy, reason, EjectReasonCode.DUPLICATE_USER, ban = false)
// send a system message to force disconnection
Sender.sendDisconnectClientSysMsg(meetingId, userId, ejectedBy, EjectReasonCode.DUPLICATE_USER, outGW)
}
}

View File

@ -3,7 +3,7 @@ package org.bigbluebutton.core.apps.users
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.models._
import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter }
import org.bigbluebutton.core2.message.senders.MsgBuilder
import org.bigbluebutton.core2.message.senders.{ MsgBuilder, Sender }
trait RegisterUserReqMsgHdlr {
this: UsersApp =>
@ -22,12 +22,44 @@ trait RegisterUserReqMsgHdlr {
val event = UserRegisteredRespMsg(header, body)
BbbCommonEnvCoreMsg(envelope, event)
}
def checkUserConcurrentAccesses(regUser: RegisteredUser): Unit = {
//Remove concurrent accesses over the limit
if (liveMeeting.props.usersProp.maxUserConcurrentAccesses > 0) {
val userConcurrentAccesses = RegisteredUsers.findAllWithExternUserId(regUser.externId, liveMeeting.registeredUsers)
.filter(u => !u.loggedOut)
.sortWith((u1, u2) => u1.registeredOn > u2.registeredOn) //Remove older first
val userAvailableSlots = liveMeeting.props.usersProp.maxUserConcurrentAccesses - userConcurrentAccesses.length
if (userAvailableSlots <= 0) {
(liveMeeting.props.usersProp.maxUserConcurrentAccesses to userConcurrentAccesses.length) foreach {
idxUserToRemove =>
{
val userToRemove = userConcurrentAccesses(idxUserToRemove - 1)
val meetingId = liveMeeting.props.meetingProp.intId
log.info(s"User ${regUser.id} with extId=${regUser.externId} has ${userConcurrentAccesses.length} concurrent accesses and limit is ${liveMeeting.props.usersProp.maxUserConcurrentAccesses}. " +
s"Ejecting the oldest=${userToRemove.id} in meetingId=${meetingId}")
val reason = "user ejected because of duplicate external userid"
UsersApp.ejectUserFromMeeting(outGW, liveMeeting, userToRemove.id, SystemUser.ID, reason, EjectReasonCode.DUPLICATE_USER, ban = false)
// send a system message to force disconnection
Sender.sendDisconnectClientSysMsg(meetingId, userToRemove.id, SystemUser.ID, EjectReasonCode.DUPLICATE_USER, outGW)
}
}
}
}
}
val guestStatus = msg.body.guestStatus
val regUser = RegisteredUsers.create(msg.body.intUserId, msg.body.extUserId,
msg.body.name, msg.body.role, msg.body.authToken,
msg.body.avatarURL, msg.body.guest, msg.body.authed, guestStatus, msg.body.excludeFromDashboard, false)
checkUserConcurrentAccesses(regUser)
RegisteredUsers.add(liveMeeting.registeredUsers, regUser)
log.info("Register user success. meetingId=" + liveMeeting.props.meetingProp.intId

View File

@ -3,8 +3,9 @@ package org.bigbluebutton.core.apps.users
import org.bigbluebutton.common2.msgs.UserJoinMeetingReqMsg
import org.bigbluebutton.core.apps.breakout.BreakoutHdlrHelpers
import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.models.{ Users2x, VoiceUsers }
import org.bigbluebutton.core.models.{ RegisteredUser, RegisteredUsers, Users2x, VoiceUsers }
import org.bigbluebutton.core.running.{ HandlerHelpers, LiveMeeting, MeetingActor, OutMsgRouter }
import org.bigbluebutton.core2.message.senders.MsgBuilder
trait UserJoinMeetingReqMsgHdlr extends HandlerHelpers {
this: MeetingActor =>
@ -26,16 +27,52 @@ trait UserJoinMeetingReqMsgHdlr extends HandlerHelpers {
state
case None =>
val newState = userJoinMeeting(outGW, msg.body.authToken, msg.body.clientType, liveMeeting, state)
if (liveMeeting.props.meetingProp.isBreakout) {
BreakoutHdlrHelpers.updateParentMeetingWithUsers(liveMeeting, eventBus)
// Check if maxParticipants has been reached
// User are able to reenter if he already joined previously with the same extId
val userHasJoinedAlready = RegisteredUsers.findWithUserId(msg.body.userId, liveMeeting.registeredUsers) match {
case Some(regUser: RegisteredUser) => RegisteredUsers.checkUserExtIdHasJoined(regUser.externId, liveMeeting.registeredUsers)
case None => false
}
val hasReachedMaxParticipants = liveMeeting.props.usersProp.maxUsers > 0 &&
RegisteredUsers.numUniqueJoinedUsers(liveMeeting.registeredUsers) >= liveMeeting.props.usersProp.maxUsers &&
userHasJoinedAlready == false
// fresh user joined (not due to reconnection). Clear (pop) the cached voice user
VoiceUsers.recoverVoiceUser(liveMeeting.voiceUsers, msg.body.userId)
if (!hasReachedMaxParticipants) {
val newState = userJoinMeeting(outGW, msg.body.authToken, msg.body.clientType, liveMeeting, state)
newState
if (liveMeeting.props.meetingProp.isBreakout) {
BreakoutHdlrHelpers.updateParentMeetingWithUsers(liveMeeting, eventBus)
}
// Warn previous users that someone connected with same Id
for {
regUser <- RegisteredUsers.getRegisteredUserWithToken(msg.body.authToken, msg.body.userId,
liveMeeting.registeredUsers)
} yield {
RegisteredUsers.findAllWithExternUserId(regUser.externId, liveMeeting.registeredUsers)
.filter(u => u.id != regUser.id)
.foreach { previousUser =>
val notifyUserEvent = MsgBuilder.buildNotifyUserInMeetingEvtMsg(
previousUser.id,
liveMeeting.props.meetingProp.intId,
"info",
"promote",
"app.mobileAppModal.userConnectedWithSameId",
"Notification to warn that user connect again from other browser/device",
Vector(regUser.name)
)
outGW.send(notifyUserEvent)
}
}
// fresh user joined (not due to reconnection). Clear (pop) the cached voice user
VoiceUsers.recoverVoiceUser(liveMeeting.voiceUsers, msg.body.userId)
newState
} else {
log.info("Ignoring user {} attempt to join, once the meeting {} has reached max participants: {}", msg.body.userId, msg.header.meetingId, liveMeeting.props.usersProp.maxUsers)
state
}
}
}
}

View File

@ -158,7 +158,6 @@ class UsersApp(
with SelectRandomViewerReqMsgHdlr
with AssignPresenterReqMsgHdlr
with ChangeUserPinStateReqMsgHdlr
with EjectDuplicateUserReqMsgHdlr
with EjectUserFromMeetingCmdMsgHdlr
with EjectUserFromMeetingSysMsgHdlr
with MuteUserCmdMsgHdlr {

View File

@ -5,7 +5,7 @@ import org.bigbluebutton.core.bus.InternalEventBus
import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.models._
import org.bigbluebutton.core.running.{ HandlerHelpers, LiveMeeting, OutMsgRouter }
import org.bigbluebutton.core2.message.senders.{ MsgBuilder, Sender }
import org.bigbluebutton.core2.message.senders.{ MsgBuilder }
trait ValidateAuthTokenReqMsgHdlr extends HandlerHelpers {
this: UsersApp =>
@ -24,10 +24,16 @@ trait ValidateAuthTokenReqMsgHdlr extends HandlerHelpers {
liveMeeting.registeredUsers)
regUser match {
case Some(u) =>
// Check if maxParticipants has been reached
// User are able to reenter if he already joined previously with the same extId
val hasReachedMaxParticipants = liveMeeting.props.usersProp.maxUsers > 0 &&
RegisteredUsers.numUniqueJoinedUsers(liveMeeting.registeredUsers) >= liveMeeting.props.usersProp.maxUsers &&
RegisteredUsers.checkUserExtIdHasJoined(u.externId, liveMeeting.registeredUsers) == false
// Check if banned user is rejoining.
// Fail validation if ejected user is rejoining.
// ralam april 21, 2020
if (u.guestStatus == GuestStatus.ALLOW && !u.banned && !u.loggedOut) {
if (u.guestStatus == GuestStatus.ALLOW && !u.banned && !u.loggedOut && !hasReachedMaxParticipants) {
userValidated(u, state)
} else {
if (u.banned) {
@ -36,6 +42,9 @@ trait ValidateAuthTokenReqMsgHdlr extends HandlerHelpers {
} else if (u.loggedOut) {
failReason = "User had logged out"
failReasonCode = EjectReasonCode.USER_LOGGED_OUT
} else if (hasReachedMaxParticipants) {
failReason = "The maximum number of participants allowed for this meeting has been reached."
failReasonCode = EjectReasonCode.MAX_PARTICIPANTS
}
validateTokenFailed(
outGW,

View File

@ -28,11 +28,7 @@ trait ClearWhiteboardPubMsgHdlr extends RightsManagementTrait {
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting)
}
} else {
for {
fullClear <- clearWhiteboard(msg.body.whiteboardId, msg.header.userId, liveMeeting)
} yield {
broadcastEvent(msg, fullClear)
}
log.error("Ignoring message ClearWhiteboardPubMsg since this functions is not available in the new Whiteboard")
}
}
}

View File

@ -21,14 +21,24 @@ trait DeleteWhiteboardAnnotationsPubMsgHdlr extends RightsManagementTrait {
bus.outGW.send(msgEvent)
}
if (filterWhiteboardMessage(msg.body.whiteboardId, msg.header.userId, liveMeeting) && permissionFailed(PermissionCheck.GUEST_LEVEL, PermissionCheck.PRESENTER_LEVEL, liveMeeting.users2x, msg.header.userId)) {
val isUserAmongPresenters = !permissionFailed(
PermissionCheck.GUEST_LEVEL,
PermissionCheck.PRESENTER_LEVEL, liveMeeting.users2x, msg.header.userId
)
val isUserModerator = !permissionFailed(
PermissionCheck.MOD_LEVEL,
PermissionCheck.VIEWER_LEVEL, liveMeeting.users2x, msg.header.userId
)
if (filterWhiteboardMessage(msg.body.whiteboardId, msg.header.userId, liveMeeting) && !isUserAmongPresenters) {
if (isNonEjectionGracePeriodOver(msg.body.whiteboardId, msg.header.userId, liveMeeting)) {
val meetingId = liveMeeting.props.meetingProp.intId
val reason = "No permission to delete an annotation."
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting)
}
} else {
val deletedAnnotations = deleteWhiteboardAnnotations(msg.body.whiteboardId, msg.header.userId, msg.body.annotationsIds, liveMeeting)
val deletedAnnotations = deleteWhiteboardAnnotations(msg.body.whiteboardId, msg.header.userId, msg.body.annotationsIds, liveMeeting, isUserAmongPresenters, isUserModerator)
if (!deletedAnnotations.isEmpty) {
broadcastEvent(msg, deletedAnnotations)
}

View File

@ -10,8 +10,8 @@ trait SendWhiteboardAnnotationsPubMsgHdlr extends RightsManagementTrait {
def handle(msg: SendWhiteboardAnnotationsPubMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
def broadcastEvent(msg: SendWhiteboardAnnotationsPubMsg, whiteboardId: String, annotations: Array[AnnotationVO]): Unit = {
val routing = Routing.addMsgToHtml5InstanceIdRouting(liveMeeting.props.meetingProp.intId, liveMeeting.props.systemProps.html5InstanceId.toString)
def broadcastEvent(msg: SendWhiteboardAnnotationsPubMsg, whiteboardId: String, annotations: Array[AnnotationVO], html5InstanceId: String): Unit = {
val routing = Routing.addMsgToHtml5InstanceIdRouting(liveMeeting.props.meetingProp.intId, html5InstanceId)
val envelope = BbbCoreEnvelope(SendWhiteboardAnnotationsEvtMsg.NAME, routing)
val header = BbbClientMsgHeader(SendWhiteboardAnnotationsEvtMsg.NAME, liveMeeting.props.meetingProp.intId, msg.header.userId)
@ -46,14 +46,19 @@ trait SendWhiteboardAnnotationsPubMsgHdlr extends RightsManagementTrait {
PermissionCheck.PRESENTER_LEVEL, liveMeeting.users2x, msg.header.userId
)
val isUserModerator = !permissionFailed(
PermissionCheck.MOD_LEVEL,
PermissionCheck.VIEWER_LEVEL, liveMeeting.users2x, msg.header.userId
)
if (isUserOneOfPermited || isUserAmongPresenters) {
println("============= Printing Sanitized annotations ============")
for (annotation <- msg.body.annotations) {
printAnnotationInfo(annotation)
}
println("============= Printed Sanitized annotations ============")
val annotations = sendWhiteboardAnnotations(msg.body.whiteboardId, msg.header.userId, msg.body.annotations, liveMeeting)
broadcastEvent(msg, msg.body.whiteboardId, annotations)
val annotations = sendWhiteboardAnnotations(msg.body.whiteboardId, msg.header.userId, msg.body.annotations, liveMeeting, isUserAmongPresenters, isUserModerator)
broadcastEvent(msg, msg.body.whiteboardId, annotations, msg.body.html5InstanceId)
} else {
//val meetingId = liveMeeting.props.meetingProp.intId
//val reason = "No permission to send a whiteboard annotation."

View File

@ -11,7 +11,7 @@ case class Whiteboard(
multiUser: Array[String],
oldMultiUser: Array[String],
changedModeOn: Long,
annotationsMap: Map[String, Map[String, AnnotationVO]]
annotationsMap: Map[String, AnnotationVO]
)
class WhiteboardApp2x(implicit val context: ActorContext)
@ -24,9 +24,16 @@ class WhiteboardApp2x(implicit val context: ActorContext)
val log = Logging(context.system, getClass)
def sendWhiteboardAnnotations(whiteboardId: String, requesterId: String, annotations: Array[AnnotationVO], liveMeeting: LiveMeeting): Array[AnnotationVO] = {
def sendWhiteboardAnnotations(
whiteboardId: String,
requesterId: String,
annotations: Array[AnnotationVO],
liveMeeting: LiveMeeting,
isPresenter: Boolean,
isModerator: Boolean
): Array[AnnotationVO] = {
// println("Received whiteboard annotation. status=[" + status + "], annotationType=[" + annotationType + "]")
liveMeeting.wbModel.addAnnotations(whiteboardId, requesterId, annotations)
liveMeeting.wbModel.addAnnotations(whiteboardId, requesterId, annotations, isPresenter, isModerator)
}
def getWhiteboardAnnotations(whiteboardId: String, liveMeeting: LiveMeeting): Array[AnnotationVO] = {
@ -34,12 +41,15 @@ class WhiteboardApp2x(implicit val context: ActorContext)
liveMeeting.wbModel.getHistory(whiteboardId)
}
def clearWhiteboard(whiteboardId: String, requesterId: String, liveMeeting: LiveMeeting): Option[Boolean] = {
liveMeeting.wbModel.clearWhiteboard(whiteboardId, requesterId)
}
def deleteWhiteboardAnnotations(whiteboardId: String, requesterId: String, annotationsIds: Array[String], liveMeeting: LiveMeeting): Array[String] = {
liveMeeting.wbModel.deleteAnnotations(whiteboardId, requesterId, annotationsIds)
def deleteWhiteboardAnnotations(
whiteboardId: String,
requesterId: String,
annotationsIds: Array[String],
liveMeeting: LiveMeeting,
isPresenter: Boolean,
isModerator: Boolean
): Array[String] = {
liveMeeting.wbModel.deleteAnnotations(whiteboardId, requesterId, annotationsIds, isPresenter, isModerator)
}
def getWhiteboardAccess(whiteboardId: String, liveMeeting: LiveMeeting): Array[String] = {

View File

@ -14,10 +14,12 @@ case class BreakoutRoom2x(
users: Vector[BreakoutUser],
voiceUsers: Vector[BreakoutVoiceUser],
startedOn: Option[Long],
started: Boolean
started: Boolean,
captureNotes: Boolean,
captureSlides: Boolean,
) {
}
case class BreakoutUser(id: String, name: String)
case class BreakoutVoiceUser(id: String, extId: String, voiceUserId: String)
case class BreakoutVoiceUser(id: String, extId: String, voiceUserId: String)

View File

@ -10,8 +10,8 @@ object BreakoutRooms {
def breakoutRoomsdurationInMinutes(status: BreakoutRooms, duration: Int) = status.breakoutRoomsdurationInMinutes = duration
def newBreakoutRoom(parentRoomId: String, id: String, externalMeetingId: String, name: String, sequence: Integer, freeJoin: Boolean,
voiceConfId: String, assignedUsers: Vector[String], breakoutRooms: BreakoutRooms): Option[BreakoutRoomVO] = {
val brvo = new BreakoutRoomVO(id, externalMeetingId, name, parentRoomId, sequence, freeJoin, voiceConfId, assignedUsers, Vector())
voiceConfId: String, assignedUsers: Vector[String], captureNotes: Boolean, captureSlides: Boolean, breakoutRooms: BreakoutRooms): Option[BreakoutRoomVO] = {
val brvo = new BreakoutRoomVO(id, externalMeetingId, name, parentRoomId, sequence, freeJoin, voiceConfId, assignedUsers, Vector(), captureNotes, captureSlides)
breakoutRooms.add(brvo)
Some(brvo)
}

View File

@ -112,7 +112,7 @@ object Polls {
shape = pollResultToWhiteboardShape(result)
annot <- send(result, shape)
} yield {
lm.wbModel.addAnnotations(annot.wbId, requesterId, Array[AnnotationVO](annot))
lm.wbModel.addAnnotations(annot.wbId, requesterId, Array[AnnotationVO](annot), false, false)
showPollResult(pollId, lm.polls)
(result, annot)
}
@ -238,7 +238,7 @@ object Polls {
private def handleRespondToTypedPoll(poll: SimplePollResultOutVO, requesterId: String, pollId: String, questionId: Int,
answer: String, lm: LiveMeeting): Option[SimplePollResultOutVO] = {
addQuestionResponse(poll.id, questionId, answer, lm.polls)
addQuestionResponse(poll.id, questionId, answer, requesterId, lm.polls)
for {
updatedPoll <- getSimplePollResult(poll.id, lm.polls)
} yield {
@ -355,6 +355,45 @@ object Polls {
pvo
}
def checkUserResponded(pollId: String, userId: String, polls: Polls): Boolean = {
polls.polls.get(pollId) match {
case Some(p) => {
if (p.getResponders().filter(p => p.userId == userId).length > 0) {
true
} else {
false
}
}
case None => false
}
}
def checkUserAddedQuestion(pollId: String, userId: String, polls: Polls): Boolean = {
polls.polls.get(pollId) match {
case Some(p) => {
if (p.getTypedPollResponders().filter(responderId => responderId == userId).length > 0) {
true
} else {
false
}
}
case None => false
}
}
def isResponsePollType(pollId: String, polls: Polls): Boolean = {
polls.polls.get(pollId) match {
case Some(p) => {
if (p.questions.filter(q => q.questionType == PollType.ResponsePollType).length > 0) {
true
} else {
false
}
}
case None => false
}
}
def showPollResult(pollId: String, polls: Polls) {
polls.get(pollId) foreach {
p =>
@ -375,10 +414,13 @@ object Polls {
}
}
def addQuestionResponse(pollId: String, questionID: Int, answer: String, polls: Polls) {
def addQuestionResponse(pollId: String, questionID: Int, answer: String, requesterId: String, polls: Polls) {
polls.polls.get(pollId) match {
case Some(p) => {
p.addQuestionResponse(questionID, answer)
if (!p.getTypedPollResponders().contains(requesterId)) {
p.addTypedPollResponder(requesterId)
p.addQuestionResponse(questionID, answer)
}
}
case None =>
}
@ -545,6 +587,7 @@ class Poll(val id: String, val questions: Array[Question], val numRespondents: I
private var _showResult: Boolean = false
private var _numResponders: Int = 0
private var _responders = new ArrayBuffer[Responder]()
private var _respondersTypedPoll = new ArrayBuffer[String]()
def showingResult() { _showResult = true }
def showResult(): Boolean = { _showResult }
@ -561,6 +604,8 @@ class Poll(val id: String, val questions: Array[Question], val numRespondents: I
def addResponder(responder: Responder) { _responders += (responder) }
def getResponders(): ArrayBuffer[Responder] = { return _responders }
def addTypedPollResponder(responderId: String) { _respondersTypedPoll += (responderId) }
def getTypedPollResponders(): ArrayBuffer[String] = { return _respondersTypedPoll }
def hasResponses(): Boolean = {
questions.foreach(q => {

View File

@ -64,6 +64,14 @@ object RegisteredUsers {
} yield user
}
def checkUserExtIdHasJoined(externId: String, regUsers: RegisteredUsers): Boolean = {
regUsers.toVector.filter(_.externId == externId).filter(_.joined).length > 0
}
def numUniqueJoinedUsers(regUsers: RegisteredUsers): Int = {
regUsers.toVector.filter(_.joined).map(_.externId).distinct.length
}
def add(users: RegisteredUsers, user: RegisteredUser): Vector[RegisteredUser] = {
findWithExternUserId(user.externId, users) match {

View File

@ -407,4 +407,5 @@ object EjectReasonCode {
val USER_INACTIVITY = "user_inactivity_eject_reason"
val BANNED_USER_REJOINING = "banned_user_rejoining_reason"
val USER_LOGGED_OUT = "user_logged_out_reason"
val MAX_PARTICIPANTS = "max_participants_reason"
}

View File

@ -65,8 +65,6 @@ class ReceivedJsonMsgHandlerActor(
// Route via meeting manager as there is a race condition if we send directly to meeting
// because the meeting actor might not have been created yet.
route[RegisterUserReqMsg](meetingManagerChannel, envelope, jsonNode)
case EjectDuplicateUserReqMsg.NAME =>
route[EjectDuplicateUserReqMsg](meetingManagerChannel, envelope, jsonNode)
case UserJoinMeetingReqMsg.NAME =>
routeGenericMsg[UserJoinMeetingReqMsg](envelope, jsonNode)
case UserJoinMeetingAfterReconnectReqMsg.NAME =>
@ -175,6 +173,10 @@ class ReceivedJsonMsgHandlerActor(
routePadMsg[PadPatchSysMsg](envelope, jsonNode)
case PadUpdatePubMsg.NAME =>
routeGenericMsg[PadUpdatePubMsg](envelope, jsonNode)
case PadCapturePubMsg.NAME =>
routePadMsg[PadCapturePubMsg](envelope, jsonNode)
case PadPinnedReqMsg.NAME =>
routeGenericMsg[PadPinnedReqMsg](envelope, jsonNode)
// Voice
case RecordingStartedVoiceConfEvtMsg.NAME =>
@ -286,6 +288,10 @@ class ReceivedJsonMsgHandlerActor(
routeGenericMsg[PreuploadedPresentationsSysPubMsg](envelope, jsonNode)
case PresentationUploadedFileTooLargeErrorSysPubMsg.NAME =>
routeGenericMsg[PresentationUploadedFileTooLargeErrorSysPubMsg](envelope, jsonNode)
case PresentationHasInvalidMimeTypeErrorSysPubMsg.NAME =>
routeGenericMsg[PresentationHasInvalidMimeTypeErrorSysPubMsg](envelope, jsonNode)
case PresentationUploadedFileTimeoutErrorSysPubMsg.NAME =>
routeGenericMsg[PresentationUploadedFileTimeoutErrorSysPubMsg](envelope, jsonNode)
case PresentationConversionUpdateSysPubMsg.NAME =>
routeGenericMsg[PresentationConversionUpdateSysPubMsg](envelope, jsonNode)
case PresentationPageCountErrorSysPubMsg.NAME =>
@ -308,10 +314,10 @@ class ReceivedJsonMsgHandlerActor(
routeGenericMsg[AssignPresenterReqMsg](envelope, jsonNode)
case MakePresentationWithAnnotationDownloadReqMsg.NAME =>
routeGenericMsg[MakePresentationWithAnnotationDownloadReqMsg](envelope, jsonNode)
case ExportPresentationWithAnnotationReqMsg.NAME =>
routeGenericMsg[ExportPresentationWithAnnotationReqMsg](envelope, jsonNode)
case NewPresAnnFileAvailableMsg.NAME =>
routeGenericMsg[NewPresAnnFileAvailableMsg](envelope, jsonNode)
case PresAnnStatusMsg.NAME =>
routeGenericMsg[PresAnnStatusMsg](envelope, jsonNode)
// Presentation Pods
case CreateNewPresentationPodPubMsg.NAME =>

View File

@ -226,7 +226,7 @@ trait HandlerHelpers extends SystemConfiguration {
model <- state.breakout
} yield {
model.rooms.values.foreach { room =>
eventBus.publish(BigBlueButtonEvent(room.id, EndBreakoutRoomInternalMsg(liveMeeting.props.breakoutProps.parentId, room.id, reason)))
eventBus.publish(BigBlueButtonEvent(room.id, EndBreakoutRoomInternalMsg(liveMeeting.props.meetingProp.intId, room.id, reason)))
}
}

View File

@ -250,7 +250,6 @@ class MeetingActor(
// Handling RegisterUserReqMsg as it is forwarded from BBBActor and
// its type is not BbbCommonEnvCoreMsg
case m: RegisterUserReqMsg => usersApp.handleRegisterUserReqMsg(m)
case m: EjectDuplicateUserReqMsg => usersApp.handleEjectDuplicateUserReqMsg(m)
case m: GetAllMeetingsReqMsg => handleGetAllMeetingsReqMsg(m)
case m: GetRunningMeetingStateReqMsg => handleGetRunningMeetingStateReqMsg(m)
case m: ValidateConnAuthTokenSysMsg => handleValidateConnAuthTokenSysMsg(m)
@ -283,7 +282,8 @@ class MeetingActor(
case msg: SendMessageToBreakoutRoomInternalMsg => state = handleSendMessageToBreakoutRoomInternalMsg(msg, state, liveMeeting, msgBus)
case msg: SendBreakoutTimeRemainingInternalMsg =>
handleSendBreakoutTimeRemainingInternalMsg(msg)
case msg: CapturePresentationReqInternalMsg => presentationPodsApp.handle(msg, state, liveMeeting, msgBus)
case msg: CaptureSharedNotesReqInternalMsg => presentationPodsApp.handle(msg, liveMeeting, msgBus)
case msg: SendRecordingTimerInternalMsg =>
state = usersApp.handleSendRecordingTimerInternalMsg(msg, state)
@ -448,9 +448,9 @@ class MeetingActor(
case m: UserTalkingInVoiceConfEvtMsg =>
updateVoiceUserLastActivity(m.body.voiceUserId)
handleUserTalkingInVoiceConfEvtMsg(m)
case m: VoiceConfCallStateEvtMsg => handleVoiceConfCallStateEvtMsg(m)
case m: VoiceConfCallStateEvtMsg => handleVoiceConfCallStateEvtMsg(m)
case m: RecordingStartedVoiceConfEvtMsg => handleRecordingStartedVoiceConfEvtMsg(m)
case m: RecordingStartedVoiceConfEvtMsg => handleRecordingStartedVoiceConfEvtMsg(m)
case m: AudioFloorChangedVoiceConfEvtMsg =>
handleAudioFloorChangedVoiceConfEvtMsg(m)
audioCaptionsApp2x.handle(m, liveMeeting)
@ -492,6 +492,7 @@ class MeetingActor(
case m: PadContentSysMsg => padsApp2x.handle(m, liveMeeting, msgBus)
case m: PadPatchSysMsg => padsApp2x.handle(m, liveMeeting, msgBus)
case m: PadUpdatePubMsg => padsApp2x.handle(m, liveMeeting, msgBus)
case m: PadPinnedReqMsg => padsApp2x.handle(m, liveMeeting, msgBus)
// Lock Settings
case m: ChangeLockSettingsInMeetingCmdMsg =>
@ -505,8 +506,9 @@ class MeetingActor(
case m: PreuploadedPresentationsSysPubMsg => presentationApp2x.handle(m, liveMeeting, msgBus)
case m: AssignPresenterReqMsg => state = handlePresenterChange(m, state)
case m: MakePresentationWithAnnotationDownloadReqMsg => presentationPodsApp.handle(m, state, liveMeeting, msgBus)
case m: ExportPresentationWithAnnotationReqMsg => presentationPodsApp.handle(m, state, liveMeeting, msgBus)
case m: NewPresAnnFileAvailableMsg => presentationPodsApp.handle(m, liveMeeting, msgBus)
case m: PresAnnStatusMsg => presentationPodsApp.handle(m, liveMeeting, msgBus)
case m: PadCapturePubMsg => presentationPodsApp.handle(m, liveMeeting, msgBus)
// Presentation Pods
case m: CreateNewPresentationPodPubMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
@ -521,6 +523,8 @@ class MeetingActor(
case m: SetPresentationDownloadablePubMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
case m: PresentationConversionUpdateSysPubMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
case m: PresentationUploadedFileTooLargeErrorSysPubMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
case m: PresentationHasInvalidMimeTypeErrorSysPubMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
case m: PresentationUploadedFileTimeoutErrorSysPubMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
case m: PresentationPageGeneratedSysPubMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
case m: PresentationPageCountErrorSysPubMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
case m: PresentationUploadTokenReqMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)

View File

@ -117,7 +117,6 @@ class AnalyticsActor(val includeChat: Boolean) extends Actor with ActorLogging {
// case m: StoreAnnotationsInRedisSysMsg => logMessage(msg)
// case m: StoreExportJobInRedisSysMsg => logMessage(msg)
case m: MakePresentationWithAnnotationDownloadReqMsg => logMessage(msg)
case m: ExportPresentationWithAnnotationReqMsg => logMessage(msg)
case m: NewPresAnnFileAvailableMsg => logMessage(msg)
case m: PresentationPageConversionStartedSysMsg => logMessage(msg)
case m: PresentationConversionEndedSysMsg => logMessage(msg)
@ -201,6 +200,7 @@ class AnalyticsActor(val includeChat: Boolean) extends Actor with ActorLogging {
case m: PadUpdatedEvtMsg => logMessage(msg)
case m: PadUpdatePubMsg => logMessage(msg)
case m: PadUpdateCmdMsg => logMessage(msg)
case m: PadCapturePubMsg => logMessage(msg)
case _ => // ignore message
}

View File

@ -85,6 +85,7 @@ case class PresentationSlide(
presentationId: String,
pageNum: Long,
setOn: Long = System.currentTimeMillis(),
presentationName: String,
)
@ -189,7 +190,7 @@ class LearningDashboardActor(
for {
page <- msg.body.presentation.pages.find(p => p.current == true)
} yield {
this.setPresentationSlide(meeting.intId, msg.body.presentation.id,page.num)
this.setPresentationSlide(meeting.intId, msg.body.presentation.id,page.num, msg.body.presentation.name)
}
}
}
@ -202,7 +203,7 @@ class LearningDashboardActor(
presentation <- presentations.get(msg.body.presentationId)
page <- presentation.pages.find(p => p.id == msg.body.pageId)
} yield {
this.setPresentationSlide(meeting.intId, msg.body.presentationId,page.num)
this.setPresentationSlide(meeting.intId, msg.body.presentationId,page.num, presentation.name)
}
}
@ -211,7 +212,7 @@ class LearningDashboardActor(
meeting <- meetings.values.find(m => m.intId == msg.header.meetingId)
} yield {
if(meeting.presentationSlides.last.presentationId == msg.body.presentationId) {
this.setPresentationSlide(meeting.intId, "",0)
this.setPresentationSlide(meeting.intId, "",0, "")
}
}
}
@ -223,7 +224,7 @@ class LearningDashboardActor(
val presPreviousSlides: Vector[PresentationSlide] = meeting.presentationSlides.filter(p => p.presentationId == msg.body.presentationId);
if(presPreviousSlides.length > 0) {
//Set last page showed for this presentation
this.setPresentationSlide(meeting.intId, msg.body.presentationId,presPreviousSlides.last.pageNum)
this.setPresentationSlide(meeting.intId, msg.body.presentationId,presPreviousSlides.last.pageNum, presPreviousSlides.last.presentationName)
} else {
//If none page was showed yet, set the current page (page 1 by default)
for {
@ -231,20 +232,20 @@ class LearningDashboardActor(
presentation <- presentations.get(msg.body.presentationId)
page <- presentation.pages.find(s => s.current == true)
} yield {
this.setPresentationSlide(meeting.intId, msg.body.presentationId,page.num)
this.setPresentationSlide(meeting.intId, msg.body.presentationId,page.num, presentation.name)
}
}
}
}
private def setPresentationSlide(meetingId: String, presentationId: String, pageNum: Long) {
private def setPresentationSlide(meetingId: String, presentationId: String, pageNum: Long, presentationName: String) {
for {
meeting <- meetings.values.find(m => m.intId == meetingId)
} yield {
if (meeting.presentationSlides.length == 0 ||
meeting.presentationSlides.last.presentationId != presentationId ||
meeting.presentationSlides.last.pageNum != pageNum) {
val updatedMeeting = meeting.copy(presentationSlides = meeting.presentationSlides :+ PresentationSlide(presentationId, pageNum))
val updatedMeeting = meeting.copy(presentationSlides = meeting.presentationSlides :+ PresentationSlide(presentationId, pageNum, presentationName = presentationName))
meetings += (updatedMeeting.intId -> updatedMeeting)
}

View File

@ -47,7 +47,7 @@ trait AppsTestFixtures {
val meetingLayout = ""
val metadata: collection.immutable.Map[String, String] = Map("foo" -> "bar", "bar" -> "baz", "baz" -> "foo")
val breakoutProps = BreakoutProps(parentId = parentMeetingId, sequence = sequence, freeJoin = false, breakoutRooms = Vector())
val breakoutProps = BreakoutProps(parentId = parentMeetingId, sequence = sequence, freeJoin = false, captureNotes = false, captureSlides = false, breakoutRooms = Vector())
val meetingProp = MeetingProp(name = meetingName, extId = externalMeetingId, intId = meetingId,
meetingCameraCap = meetingCameraCap,

View File

@ -18,7 +18,7 @@ val compileSettings = Seq(
"-Xlint",
"-Ywarn-dead-code",
"-language:_",
"-target:jvm-1.11",
"-target:11",
"-encoding", "UTF-8"
),
javacOptions ++= List(
@ -27,7 +27,7 @@ val compileSettings = Seq(
)
)
scalaVersion := "2.13.4"
scalaVersion := "2.13.9"
resolvers += Resolver.sonatypeRepo("releases")

View File

@ -7,7 +7,7 @@ object Dependencies {
object Versions {
// Scala
val scala = "2.13.4"
val scala = "2.13.9"
val junitInterface = "0.11"
val scalactic = "3.0.8"
@ -21,8 +21,8 @@ object Dependencies {
val codec = "1.15"
// BigBlueButton
val bbbCommons = "0.0.21-SNAPSHOT"
val bbbFsesl = "0.0.8-SNAPSHOT"
val bbbCommons = "0.0.22-SNAPSHOT"
val bbbFsesl = "0.0.9-SNAPSHOT"
// Test
val scalaTest = "3.2.11"

View File

@ -1,7 +1,7 @@
import org.bigbluebutton.build._
version := "0.0.21-SNAPSHOT"
version := "0.0.22-SNAPSHOT"
val compileSettings = Seq(
organization := "org.bigbluebutton",
@ -12,7 +12,7 @@ val compileSettings = Seq(
"-Xlint",
"-Ywarn-dead-code",
"-language:_",
"-target:jvm-1.11",
"-target:11",
"-encoding", "UTF-8"
),
javacOptions ++= List(
@ -55,7 +55,7 @@ scalariformAutoformat := true
// Do not append Scala versions to the generated artifacts
//crossPaths := false
scalaVersion := "2.13.4"
scalaVersion := "2.13.9"
// This forbids including Scala related libraries into the dependency
//autoScalaLibrary := false

View File

@ -7,7 +7,7 @@ object Dependencies {
object Versions {
// Scala
val scala = "2.13.4"
val scala = "2.13.9"
val junit = "4.12"
val junitInterface = "0.11"
val scalactic = "3.0.8"

View File

@ -9,16 +9,16 @@ case class DurationProps(duration: Int, createdTime: Long, createdDate: String,
endWhenNoModerator: Boolean, endWhenNoModeratorDelayInMinutes: Int)
case class MeetingProp(
name: String,
extId: String,
intId: String,
meetingCameraCap: Int,
maxPinnedCameras: Int,
isBreakout: Boolean,
disabledFeatures: Vector[String],
notifyRecordingIsOn: Boolean,
uploadExternalDescription: String,
uploadExternalUrl: String,
name: String,
extId: String,
intId: String,
meetingCameraCap: Int,
maxPinnedCameras: Int,
isBreakout: Boolean,
disabledFeatures: Vector[String],
notifyRecordingIsOn: Boolean,
presentationUploadExternalDescription: String,
presentationUploadExternalUrl: String,
)
case class BreakoutProps(
@ -27,7 +27,9 @@ case class BreakoutProps(
freeJoin: Boolean,
breakoutRooms: Vector[String],
record: Boolean,
privateChatEnabled: Boolean
privateChatEnabled: Boolean,
captureNotes: Boolean,
captureSlides: Boolean,
)
case class PasswordProp(moderatorPass: String, viewerPass: String, learningDashboardAccessToken: String)
@ -39,14 +41,15 @@ case class WelcomeProp(welcomeMsgTemplate: String, welcomeMsg: String, modOnlyMe
case class VoiceProp(telVoice: String, voiceConf: String, dialNumber: String, muteOnStart: Boolean)
case class UsersProp(
maxUsers: Int,
webcamsOnlyForModerator: Boolean,
userCameraCap: Int,
guestPolicy: String,
meetingLayout: String,
allowModsToUnmuteUsers: Boolean,
allowModsToEjectCameras: Boolean,
authenticatedGuest: Boolean
maxUsers: Int,
maxUserConcurrentAccesses:Int,
webcamsOnlyForModerator: Boolean,
userCameraCap: Int,
guestPolicy: String,
meetingLayout: String,
allowModsToUnmuteUsers: Boolean,
allowModsToEjectCameras: Boolean,
authenticatedGuest: Boolean
)
case class MetadataProp(metadata: collection.immutable.Map[String, String])

View File

@ -3,7 +3,7 @@ package org.bigbluebutton.common2.domain
case class PresentationVO(id: String, temporaryPresentationId: String, name: String, current: Boolean = false,
pages: Vector[PageVO], downloadable: Boolean, removable: Boolean)
case class PageVO(id: String, num: Int, thumbUri: String = "", swfUri: String,
case class PageVO(id: String, num: Int, thumbUri: String = "",
txtUri: String, svgUri: String, current: Boolean = false, xOffset: Double = 0,
yOffset: Double = 0, widthRatio: Double = 100D, heightRatio: Double = 100D)

View File

@ -13,7 +13,7 @@ case class BreakoutRoomJoinURLEvtMsgBody(parentId: String, breakoutId: String, e
object BreakoutRoomsListEvtMsg { val NAME = "BreakoutRoomsListEvtMsg" }
case class BreakoutRoomsListEvtMsg(header: BbbClientMsgHeader, body: BreakoutRoomsListEvtMsgBody) extends BbbCoreMsg
case class BreakoutRoomsListEvtMsgBody(meetingId: String, rooms: Vector[BreakoutRoomInfo], roomsReady: Boolean)
case class BreakoutRoomInfo(name: String, externalId: String, breakoutId: String, sequence: Int, shortName: String, isDefaultName: Boolean, freeJoin: Boolean, html5JoinUrls: Map[String, String])
case class BreakoutRoomInfo(name: String, externalId: String, breakoutId: String, sequence: Int, shortName: String, isDefaultName: Boolean, freeJoin: Boolean, html5JoinUrls: Map[String, String], captureNotes: Boolean, captureSlides: Boolean)
object BreakoutRoomsListMsg { val NAME = "BreakoutRoomsListMsg" }
case class BreakoutRoomsListMsg(header: BbbClientMsgHeader, body: BreakoutRoomsListMsgBody) extends StandardMsg
@ -58,7 +58,9 @@ case class BreakoutRoomDetail(
sourcePresentationId: String,
sourcePresentationSlide: Int,
record: Boolean,
privateChatEnabled: Boolean
privateChatEnabled: Boolean,
captureNotes: Boolean,
captureSlides: Boolean,
)
/**
@ -66,7 +68,7 @@ case class BreakoutRoomDetail(
*/
object CreateBreakoutRoomsCmdMsg { val NAME = "CreateBreakoutRoomsCmdMsg" }
case class CreateBreakoutRoomsCmdMsg(header: BbbClientMsgHeader, body: CreateBreakoutRoomsCmdMsgBody) extends StandardMsg
case class CreateBreakoutRoomsCmdMsgBody(meetingId: String, durationInMinutes: Int, record: Boolean, rooms: Vector[BreakoutRoomMsgBody])
case class CreateBreakoutRoomsCmdMsgBody(meetingId: String, durationInMinutes: Int, record: Boolean, captureNotes: Boolean, captureSlides: Boolean, rooms: Vector[BreakoutRoomMsgBody])
case class BreakoutRoomMsgBody(name: String, sequence: Int, shortName: String, isDefaultName: Boolean, freeJoin: Boolean, users: Vector[String])
// Sent by user to request ending all the breakout rooms
@ -123,5 +125,5 @@ case class BreakoutUserVO(id: String, name: String)
case class BreakoutRoomVO(id: String, externalId: String, name: String, parentId: String,
sequence: Int, freeJoin: Boolean, voiceConf: String,
assignedUsers: Vector[String], users: Vector[BreakoutUserVO])
assignedUsers: Vector[String], users: Vector[BreakoutUserVO], captureNotes: Boolean, captureSlides: Boolean)

View File

@ -113,3 +113,18 @@ case class PadUpdatePubMsgBody(externalId: String, text: String)
object PadUpdateCmdMsg { val NAME = "PadUpdateCmdMsg" }
case class PadUpdateCmdMsg(header: BbbCoreHeaderWithMeetingId, body: PadUpdateCmdMsgBody) extends BbbCoreMsg
case class PadUpdateCmdMsgBody(groupId: String, name: String, text: String)
// pads -> apps
object PadCapturePubMsg { val NAME = "PadCapturePubMsg" }
case class PadCapturePubMsg(header: BbbCoreHeaderWithMeetingId, body: PadCapturePubMsgBody) extends PadStandardMsg
case class PadCapturePubMsgBody(parentMeetingId: String, breakoutId: String, padId: String, filename: String)
// client -> apps
object PadPinnedReqMsg { val NAME = "PadPinnedReqMsg" }
case class PadPinnedReqMsg(header: BbbClientMsgHeader, body: PadPinnedReqMsgBody) extends StandardMsg
case class PadPinnedReqMsgBody(externalId: String, pinned: Boolean)
// apps -> client
object PadPinnedEvtMsg { val NAME = "PadPinnedEvtMsg" }
case class PadPinnedEvtMsg(header: BbbClientMsgHeader, body: PadPinnedEvtMsgBody) extends BbbCoreMsg
case class PadPinnedEvtMsgBody(externalId: String, pinned: Boolean)

View File

@ -14,17 +14,13 @@ object MakePresentationWithAnnotationDownloadReqMsg { val NAME = "MakePresentati
case class MakePresentationWithAnnotationDownloadReqMsg(header: BbbClientMsgHeader, body: MakePresentationWithAnnotationDownloadReqMsgBody) extends StandardMsg
case class MakePresentationWithAnnotationDownloadReqMsgBody(presId: String, allPages: Boolean, pages: List[Int])
object ExportPresentationWithAnnotationReqMsg { val NAME = "ExportPresentationWithAnnotationReqMsg" }
case class ExportPresentationWithAnnotationReqMsg(header: BbbClientMsgHeader, body: ExportPresentationWithAnnotationReqMsgBody) extends StandardMsg
case class ExportPresentationWithAnnotationReqMsgBody(parentMeetingId: String, allPages: Boolean)
object NewPresAnnFileAvailableMsg { val NAME = "NewPresAnnFileAvailableMsg" }
case class NewPresAnnFileAvailableMsg(header: BbbClientMsgHeader, body: NewPresAnnFileAvailableMsgBody) extends StandardMsg
case class NewPresAnnFileAvailableMsgBody(fileURI: String, presId: String)
object NewPresAnnFileAvailableEvtMsg { val NAME = "NewPresAnnFileAvailableEvtMsg" }
case class NewPresAnnFileAvailableEvtMsg(header: BbbClientMsgHeader, body: NewPresAnnFileAvailableEvtMsgBody) extends BbbCoreMsg
case class NewPresAnnFileAvailableEvtMsgBody(fileURI: String, presId: String)
object PresAnnStatusMsg { val NAME = "PresAnnStatusMsg" }
case class PresAnnStatusMsg(header: BbbClientMsgHeader, body: PresAnnStatusMsgBody) extends StandardMsg
case class PresAnnStatusMsgBody(presId: String, pageNumber: Int, totalPages: Int, status: String, error: Boolean);
// ------------ bbb-common-web to akka-apps ------------
@ -40,4 +36,17 @@ case class PresenterUnassignedEvtMsgBody(intId: String, name: String, assignedBy
object NewPresentationEvtMsg { val NAME = "NewPresentationEvtMsg" }
case class NewPresentationEvtMsg(header: BbbClientMsgHeader, body: NewPresentationEvtMsgBody) extends BbbCoreMsg
case class NewPresentationEvtMsgBody(presentation: PresentationVO)
object NewPresAnnFileAvailableEvtMsg { val NAME = "NewPresAnnFileAvailableEvtMsg" }
case class NewPresAnnFileAvailableEvtMsg(header: BbbClientMsgHeader, body: NewPresAnnFileAvailableEvtMsgBody) extends BbbCoreMsg
case class NewPresAnnFileAvailableEvtMsgBody(fileURI: String, presId: String)
object PresAnnStatusEvtMsg { val NAME = "PresAnnStatusEvtMsg" }
case class PresAnnStatusEvtMsg(header: BbbClientMsgHeader, body: PresAnnStatusEvtMsgBody) extends BbbCoreMsg
case class PresAnnStatusEvtMsgBody(presId: String, pageNumber: Int, totalPages: Int, status: String, error: Boolean);
object CaptureSharedNotesReqEvtMsg { val NAME = "CaptureSharedNotesReqEvtMsg" }
case class CaptureSharedNotesReqEvtMsg(header: BbbClientMsgHeader, body: CaptureSharedNotesReqEvtMsgBody) extends BbbCoreMsg
case class CaptureSharedNotesReqEvtMsgBody(parentMeetingId: String, meetingName: String)
// ------------ akka-apps to client ------------

View File

@ -52,11 +52,12 @@ case class PresentationConversionUpdateSysPubMsg(
body: PresentationConversionUpdateSysPubMsgBody
) extends StandardMsg
case class PresentationConversionUpdateSysPubMsgBody(
podId: String,
messageKey: String,
code: String,
presentationId: String,
presName: String
podId: String,
messageKey: String,
code: String,
presentationId: String,
presName: String,
temporaryPresentationId: String
)
object PresentationPageCountErrorSysPubMsg { val NAME = "PresentationPageCountErrorSysPubMsg" }
@ -165,6 +166,39 @@ case class PresentationUploadedFileTooLargeErrorSysPubMsgBody(
maxFileSize: Int
)
object PresentationHasInvalidMimeTypeErrorSysPubMsg { val NAME = "PresentationHasInvalidMimeTypeErrorSysPubMsg" }
case class PresentationHasInvalidMimeTypeErrorSysPubMsg(
header: BbbClientMsgHeader,
body: PresentationHasInvalidMimeTypeErrorSysPubMsgBody
) extends StandardMsg
case class PresentationHasInvalidMimeTypeErrorSysPubMsgBody(
podId: String,
meetingId: String,
presentationName: String,
temporaryPresentationId: String,
presentationId: String,
messageKey: String,
fileMime: String,
fileExtension: String,
)
object PresentationUploadedFileTimeoutErrorSysPubMsg { val NAME = "PresentationUploadedFileTimeoutErrorSysPubMsg" }
case class PresentationUploadedFileTimeoutErrorSysPubMsg(
header: BbbClientMsgHeader,
body: PresentationUploadedFileTimeoutErrorSysPubMsgBody
) extends StandardMsg
case class PresentationUploadedFileTimeoutErrorSysPubMsgBody(
podId: String,
meetingId: String,
presentationName: String,
page: Int,
messageKey: String,
temporaryPresentationId: String,
presentationId: String,
maxNumberOfAttempts: Int,
)
// ------------ bbb-common-web to akka-apps ------------
// ------------ akka-apps to client ------------
@ -190,7 +224,7 @@ case class PresentationUploadTokenFailRespMsgBody(podId: String, filename: Strin
object PresentationConversionUpdateEvtMsg { val NAME = "PresentationConversionUpdateEvtMsg" }
case class PresentationConversionUpdateEvtMsg(header: BbbClientMsgHeader, body: PresentationConversionUpdateEvtMsgBody) extends BbbCoreMsg
case class PresentationConversionUpdateEvtMsgBody(podId: String, messageKey: String, code: String, presentationId: String, presName: String)
case class PresentationConversionUpdateEvtMsgBody(podId: String, messageKey: String, code: String, presentationId: String, presName: String, temporaryPresentationId: String)
object PresentationPageCountErrorEvtMsg { val NAME = "PresentationPageCountErrorEvtMsg" }
case class PresentationPageCountErrorEvtMsg(header: BbbClientMsgHeader, body: PresentationPageCountErrorEvtMsgBody) extends BbbCoreMsg
@ -220,6 +254,20 @@ object PresentationUploadedFileTooLargeErrorEvtMsg { val NAME = "PresentationUpl
case class PresentationUploadedFileTooLargeErrorEvtMsg(header: BbbClientMsgHeader, body: PresentationUploadedFileTooLargeErrorEvtMsgBody) extends BbbCoreMsg
case class PresentationUploadedFileTooLargeErrorEvtMsgBody(podId: String, messageKey: String, code: String, presentationName: String, presentationToken: String, fileSize: Int, maxFileSize: Int)
object PresentationHasInvalidMimeTypeErrorEvtMsg { val NAME = "PresentationHasInvalidMimeTypeErrorEvtMsg" }
case class PresentationHasInvalidMimeTypeErrorEvtMsg(header: BbbClientMsgHeader, body: PresentationHasInvalidMimeTypeErrorEvtMsgBody) extends BbbCoreMsg
case class PresentationHasInvalidMimeTypeErrorEvtMsgBody(podId: String, meetingId: String, presentationName: String,
temporaryPresentationId: String, presentationId: String,
messageKey: String, fileMime: String, fileExtension: String,
)
object PresentationUploadedFileTimeoutErrorEvtMsg { val NAME = "PresentationUploadedFileTimeoutErrorEvtMsg" }
case class PresentationUploadedFileTimeoutErrorEvtMsg(header: BbbClientMsgHeader, body: PresentationUploadedFileTimeoutErrorEvtMsgBody) extends BbbCoreMsg
case class PresentationUploadedFileTimeoutErrorEvtMsgBody(podId: String, meetingId: String, presentationName: String,
page: Int, messageKey: String,
temporaryPresentationId: String, presentationId: String,
maxNumberOfAttempts: Int)
object PresentationConversionRequestReceivedEventMsg { val NAME = "PresentationConversionRequestReceivedEventMsg" }
case class PresentationConversionRequestReceivedEventMsg(
header: BbbClientMsgHeader,

View File

@ -1,13 +1,5 @@
package org.bigbluebutton.common2.msgs
object EjectDuplicateUserReqMsg { val NAME = "EjectDuplicateUserReqMsg" }
case class EjectDuplicateUserReqMsg(
header: BbbCoreHeaderWithMeetingId,
body: EjectDuplicateUserReqMsgBody
) extends BbbCoreMsg
case class EjectDuplicateUserReqMsgBody(meetingId: String, intUserId: String, name: String,
extUserId: String)
object RegisterUserReqMsg { val NAME = "RegisterUserReqMsg" }
case class RegisterUserReqMsg(
header: BbbCoreHeaderWithMeetingId,

View File

@ -53,7 +53,7 @@ case class SendCursorPositionPubMsgBody(whiteboardId: String, xPercent: Double,
object SendWhiteboardAnnotationsPubMsg { val NAME = "SendWhiteboardAnnotationsPubMsg" }
case class SendWhiteboardAnnotationsPubMsg(header: BbbClientMsgHeader, body: SendWhiteboardAnnotationsPubMsgBody) extends StandardMsg
case class SendWhiteboardAnnotationsPubMsgBody(whiteboardId: String, annotations: Array[AnnotationVO])
case class SendWhiteboardAnnotationsPubMsgBody(whiteboardId: String, annotations: Array[AnnotationVO], html5InstanceId: String)
object DeleteWhiteboardAnnotationsPubMsg { val NAME = "DeleteWhiteboardAnnotationsPubMsg" }
case class DeleteWhiteboardAnnotationsPubMsg(header: BbbClientMsgHeader, body: DeleteWhiteboardAnnotationsPubMsgBody) extends StandardMsg

View File

@ -49,7 +49,7 @@ trait TestFixtures {
meetingCameraCap = meetingCameraCap,
maxPinnedCameras = maxPinnedCameras,
isBreakout = isBreakout.booleanValue())
val breakoutProps = BreakoutProps(parentId = parentMeetingId, sequence = sequence, freeJoin = false, breakoutRooms = Vector())
val breakoutProps = BreakoutProps(parentId = parentMeetingId, sequence = sequence, freeJoin = false, captureNotes = false, captureSlides = false, breakoutRooms = Vector())
val durationProps = DurationProps(duration = durationInMinutes, createdTime = createTime, createdDate = createDate,
meetingExpireIfNoUserJoinedInMinutes = meetingExpireIfNoUserJoinedInMinutes, meetingExpireWhenLastUserLeftInMinutes = meetingExpireWhenLastUserLeftInMinutes,

View File

@ -11,7 +11,7 @@ val compileSettings = Seq(
"-Xlint",
"-Ywarn-dead-code",
"-language:_",
"-target:jvm-1.11",
"-target:11",
"-encoding", "UTF-8"
),
javacOptions ++= List(
@ -40,7 +40,7 @@ lazy val commonWeb = (project in file(".")).settings(name := "bbb-common-web", l
// Config file is in ./.scalariform.conf
scalariformAutoformat := true
scalaVersion := "2.13.4"
scalaVersion := "2.13.9"
//-----------
// Packaging
//
@ -102,7 +102,7 @@ licenses := Seq("LGPL-3.0" -> url("http://opensource.org/licenses/LGPL-3.0"))
homepage := Some(url("http://www.bigbluebutton.org"))
libraryDependencies += "javax.validation" % "validation-api" % "2.0.1.Final"
libraryDependencies += "org.springframework.boot" % "spring-boot-starter-validation" % "2.5.1"
libraryDependencies += "org.springframework.boot" % "spring-boot-starter-validation" % "2.7.1"
libraryDependencies += "org.glassfish" % "javax.el" % "3.0.1-b12"
libraryDependencies += "org.apache.httpcomponents" % "httpclient" % "4.5.13"

View File

@ -7,7 +7,7 @@ object Dependencies {
object Versions {
// Scala
val scala = "2.13.4"
val scala = "2.13.9"
val junit = "4.12"
val junitInterface = "0.11"
val scalactic = "3.0.8"
@ -31,10 +31,10 @@ object Dependencies {
val lang = "3.12.0"
val io = "2.11.0"
val pool = "2.11.1"
val text = "1.9"
val text = "1.10.0"
// BigBlueButton
val bbbCommons = "0.0.21-SNAPSHOT"
val bbbCommons = "0.0.22-SNAPSHOT"
// Test
val scalaTest = "3.2.11"

View File

@ -73,9 +73,11 @@ public class ApiParams {
public static final String DISABLED_FEATURES = "disabledFeatures";
public static final String NOTIFY_RECORDING_IS_ON = "notifyRecordingIsOn";
public static final String UPLOAD_EXTERNAL_DESCRIPTION = "uploadExternalDescription";
public static final String UPLOAD_EXTERNAL_URL = "uploadExternalUrl";
public static final String PRESENTATION_UPLOAD_EXTERNAL_DESCRIPTION = "presentationUploadExternalDescription";
public static final String PRESENTATION_UPLOAD_EXTERNAL_URL = "presentationUploadExternalUrl";
public static final String BREAKOUT_ROOMS_CAPTURE_SLIDES = "breakoutRoomsCaptureSlides";
public static final String BREAKOUT_ROOMS_CAPTURE_NOTES = "breakoutRoomsCaptureNotes";
public static final String BREAKOUT_ROOMS_ENABLED = "breakoutRoomsEnabled";
public static final String BREAKOUT_ROOMS_RECORD = "breakoutRoomsRecord";
public static final String BREAKOUT_ROOMS_PRIVATE_CHAT_ENABLED = "breakoutRoomsPrivateChatEnabled";

View File

@ -19,9 +19,7 @@
package org.bigbluebutton.api;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.AbstractMap;
import java.util.Collection;
import java.util.Collections;
@ -43,14 +41,12 @@ import java.util.concurrent.LinkedBlockingQueue;
import com.google.gson.JsonObject;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.utils.URIBuilder;
import org.bigbluebutton.api.HTML5LoadBalancingService;
import org.bigbluebutton.api.domain.GuestPolicy;
import org.bigbluebutton.api.domain.Meeting;
import org.bigbluebutton.api.domain.Recording;
import org.bigbluebutton.api.domain.RegisteredUser;
import org.bigbluebutton.api.domain.User;
import org.bigbluebutton.api.domain.UserSession;
import org.bigbluebutton.api.domain.MeetingLayout;
import org.bigbluebutton.api.messaging.MessageListener;
import org.bigbluebutton.api.messaging.converters.messages.DestroyMeetingMessage;
import org.bigbluebutton.api.messaging.converters.messages.EndMeetingMessage;
@ -60,10 +56,9 @@ import org.bigbluebutton.api.messaging.converters.messages.DeletedRecordingMessa
import org.bigbluebutton.api.messaging.messages.*;
import org.bigbluebutton.api2.IBbbWebApiGWApp;
import org.bigbluebutton.api2.domain.UploadedTrack;
import org.bigbluebutton.common2.msgs.MeetingCreatedEvtMsg;
import org.bigbluebutton.common2.redis.RedisStorageService;
import org.bigbluebutton.presentation.PresentationUrlDownloadService;
import org.bigbluebutton.presentation.imp.SwfSlidesGenerationProgressNotifier;
import org.bigbluebutton.presentation.imp.SlidesGenerationProgressNotifier;
import org.bigbluebutton.web.services.WaitingGuestCleanupTimerTask;
import org.bigbluebutton.web.services.UserCleanupTimerTask;
import org.bigbluebutton.web.services.EnteredUserCleanupTimerTask;
@ -77,7 +72,6 @@ import com.google.gson.Gson;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.InputStream;
import org.springframework.data.domain.*;
@ -104,8 +98,7 @@ public class MeetingService implements MessageListener {
private StunTurnService stunTurnService;
private RedisStorageService storeService;
private CallbackUrlService callbackUrlService;
private HTML5LoadBalancingService html5LoadBalancingService;
private SwfSlidesGenerationProgressNotifier notifier;
private SlidesGenerationProgressNotifier notifier;
private long usersTimeout;
private long waitingGuestUsersTimeout;
@ -377,6 +370,8 @@ public class MeetingService implements MessageListener {
breakoutMetadata.put("meetingId", m.getExternalId());
breakoutMetadata.put("sequence", m.getSequence().toString());
breakoutMetadata.put("freeJoin", m.isFreeJoin().toString());
breakoutMetadata.put("captureSlides", m.isCaptureSlides().toString());
breakoutMetadata.put("captureNotes", m.isCaptureNotes().toString());
breakoutMetadata.put("parentMeetingId", m.getParentMeetingId());
storeService.recordBreakoutInfo(m.getInternalId(), breakoutMetadata);
}
@ -388,6 +383,8 @@ public class MeetingService implements MessageListener {
if (m.isBreakout()) {
logData.put("sequence", m.getSequence());
logData.put("freeJoin", m.isFreeJoin());
logData.put("captureSlides", m.isCaptureSlides());
logData.put("captureNotes", m.isCaptureNotes());
logData.put("parentMeetingId", m.getParentMeetingId());
}
logData.put("name", m.getName());
@ -415,14 +412,14 @@ public class MeetingService implements MessageListener {
m.getLearningDashboardAccessToken(), m.getCreateTime(),
formatPrettyDate(m.getCreateTime()), m.isBreakout(), m.getSequence(), m.isFreeJoin(), m.getMetadata(),
m.getGuestPolicy(), m.getAuthenticatedGuest(), m.getMeetingLayout(), m.getWelcomeMessageTemplate(), m.getWelcomeMessage(), m.getModeratorOnlyMessage(),
m.getDialNumber(), m.getMaxUsers(),
m.getDialNumber(), m.getMaxUsers(), m.getMaxUserConcurrentAccesses(),
m.getMeetingExpireIfNoUserJoinedInMinutes(), m.getMeetingExpireWhenLastUserLeftInMinutes(),
m.getUserInactivityInspectTimerInMinutes(), m.getUserInactivityThresholdInMinutes(),
m.getUserActivitySignResponseDelayInMinutes(), m.getEndWhenNoModerator(), m.getEndWhenNoModeratorDelayInMinutes(),
m.getMuteOnStart(), m.getAllowModsToUnmuteUsers(), m.getAllowModsToEjectCameras(), m.getMeetingKeepEvents(),
m.breakoutRoomsParams, m.lockSettingsParams, m.getHtml5InstanceId(),
m.getGroups(), m.getDisabledFeatures(), m.getNotifyRecordingIsOn(),
m.getUploadExternalDescription(), m.getUploadExternalUrl());
m.getPresentationUploadExternalDescription(), m.getPresentationUploadExternalUrl());
}
private String formatPrettyDate(Long timestamp) {
@ -434,33 +431,6 @@ public class MeetingService implements MessageListener {
}
private void processRegisterUser(RegisterUser message) {
Meeting m = getMeeting(message.meetingID);
if (m != null) {
User prevUser = m.getUserWithExternalId(message.externUserID);
if (prevUser != null) {
Map<String, Object> logData = new HashMap<>();
logData.put("meetingId", m.getInternalId());
logData.put("externalMeetingId", m.getExternalId());
logData.put("name", m.getName());
logData.put("extUserId", prevUser.getExternalUserId());
logData.put("intUserId", prevUser.getInternalUserId());
logData.put("username", prevUser.getFullname());
logData.put("logCode", "duplicate_user_with_external_userid");
logData.put("description", "Duplicate user with external userid.");
Gson gson = new Gson();
String logStr = gson.toJson(logData);
log.info(" --analytics-- data={}", logStr);
if (!m.allowDuplicateExtUserid) {
gw.ejectDuplicateUser(message.meetingID,
prevUser.getInternalUserId(), prevUser.getFullname(),
prevUser.getExternalUserId());
}
}
}
gw.registerUser(message.meetingID,
message.internalUserId, message.fullname, message.role,
message.externUserID, message.authToken, message.avatarURL, message.guest,
@ -661,6 +631,8 @@ public class MeetingService implements MessageListener {
params.put(ApiParams.IS_BREAKOUT, "true");
params.put(ApiParams.SEQUENCE, message.sequence.toString());
params.put(ApiParams.FREE_JOIN, message.freeJoin.toString());
params.put(ApiParams.BREAKOUT_ROOMS_CAPTURE_SLIDES, message.captureSlides.toString());
params.put(ApiParams.BREAKOUT_ROOMS_CAPTURE_NOTES, message.captureNotes.toString());
params.put(ApiParams.ATTENDEE_PW, message.viewerPassword);
params.put(ApiParams.MODERATOR_PW, message.moderatorPassword);
params.put(ApiParams.DIAL_NUMBER, message.dialNumber);
@ -951,9 +923,8 @@ public class MeetingService implements MessageListener {
message.name, message.role, message.avatarURL, message.guest, message.guestStatus,
message.clientType);
if(m.getMaxUsers() > 0 && m.getUsers().size() >= m.getMaxUsers()) {
if(m.getMaxUsers() > 0 && m.countUniqueExtIds() >= m.getMaxUsers()) {
m.removeEnteredUser(user.getInternalUserId());
gw.ejectDuplicateUser(message.meetingId, user.getInternalUserId(), user.getFullname(), user.getExternalUserId());
return;
}
@ -1345,7 +1316,7 @@ public class MeetingService implements MessageListener {
enteredUsersTimeout = value;
}
public void setSwfSlidesGenerationProgressNotifier(SwfSlidesGenerationProgressNotifier notifier) {
public void setSlidesGenerationProgressNotifier(SlidesGenerationProgressNotifier notifier) {
this.notifier = notifier;
}

View File

@ -47,6 +47,7 @@ import org.bigbluebutton.api.domain.BreakoutRoomsParams;
import org.bigbluebutton.api.domain.LockSettingsParams;
import org.bigbluebutton.api.domain.Meeting;
import org.bigbluebutton.api.domain.Group;
import org.bigbluebutton.api.service.ServiceUtils;
import org.bigbluebutton.api.util.ParamsUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -66,6 +67,8 @@ public class ParamsProcessorUtil {
private String apiVersion;
private boolean serviceEnabled = false;
private String securitySalt;
private String supportedChecksumAlgorithms;
private String checksumHash;
private int defaultMaxUsers = 20;
private String defaultWelcomeMessage;
private String defaultWelcomeMessageFooter;
@ -78,6 +81,7 @@ public class ParamsProcessorUtil {
private String defaultHTML5ClientUrl;
private String defaultGuestWaitURL;
private Boolean allowRequestsWithoutSession = false;
private Integer defaultHttpSessionTimeout = 14400;
private Boolean useDefaultAvatar = false;
private String defaultAvatarURL;
private String defaultGuestPolicy;
@ -101,11 +105,13 @@ public class ParamsProcessorUtil {
private boolean defaultKeepEvents = false;
private Boolean useDefaultLogo;
private String defaultLogoURL;
private String defaultUploadExternalDescription = "";
private String defaultUploadExternalUrl = "";
private String defaultPresentationUploadExternalDescription = "";
private String defaultPresentationUploadExternalUrl = "";
private boolean defaultBreakoutRoomsEnabled = true;
private boolean defaultBreakoutRoomsRecord;
private boolean defaultBreakoutRoomsCaptureSlides = false;
private boolean defaultBreakoutRoomsCaptureNotes = false;
private boolean defaultbreakoutRoomsPrivateChatEnabled;
private boolean defaultLockSettingsDisableCam;
@ -128,6 +134,8 @@ public class ParamsProcessorUtil {
private Integer userInactivityThresholdInMinutes = 30;
private Integer userActivitySignResponseDelayInMinutes = 5;
private Boolean defaultAllowDuplicateExtUserid = true;
private Integer maxUserConcurrentAccesses = 0;
private Boolean defaultEndWhenNoModerator = false;
private Integer defaultEndWhenNoModeratorDelayInMinutes = 1;
private Integer defaultHtml5InstanceId = 1;
@ -275,7 +283,19 @@ public class ParamsProcessorUtil {
breakoutRoomsPrivateChatEnabled = Boolean.parseBoolean(breakoutRoomsPrivateChatEnabledParam);
}
return new BreakoutRoomsParams(breakoutRoomsRecord, breakoutRoomsPrivateChatEnabled);
Boolean breakoutRoomsCaptureSlides = defaultBreakoutRoomsCaptureSlides;
String breakoutRoomsCaptureParam = params.get(ApiParams.BREAKOUT_ROOMS_CAPTURE_SLIDES);
if (!StringUtils.isEmpty(breakoutRoomsCaptureParam)) {
breakoutRoomsCaptureSlides = Boolean.parseBoolean(breakoutRoomsCaptureParam);
}
Boolean breakoutRoomsCaptureNotes = defaultBreakoutRoomsCaptureNotes;
String breakoutRoomsCaptureNotesParam = params.get(ApiParams.BREAKOUT_ROOMS_CAPTURE_NOTES);
if (!StringUtils.isEmpty(breakoutRoomsCaptureNotesParam)) {
breakoutRoomsCaptureNotes = Boolean.parseBoolean(breakoutRoomsCaptureNotesParam);
}
return new BreakoutRoomsParams(breakoutRoomsRecord, breakoutRoomsPrivateChatEnabled, breakoutRoomsCaptureNotes, breakoutRoomsCaptureSlides);
}
private LockSettingsParams processLockSettingsParams(Map<String, String> params) {
@ -620,14 +640,14 @@ public class ParamsProcessorUtil {
guestPolicy = params.get(ApiParams.GUEST_POLICY);
}
String uploadExternalDescription = defaultUploadExternalDescription;
if (!StringUtils.isEmpty(params.get(ApiParams.UPLOAD_EXTERNAL_DESCRIPTION))) {
uploadExternalDescription = params.get(ApiParams.UPLOAD_EXTERNAL_DESCRIPTION);
String presentationUploadExternalDescription = defaultPresentationUploadExternalDescription;
if (!StringUtils.isEmpty(params.get(ApiParams.PRESENTATION_UPLOAD_EXTERNAL_DESCRIPTION))) {
presentationUploadExternalDescription = params.get(ApiParams.PRESENTATION_UPLOAD_EXTERNAL_DESCRIPTION);
}
String uploadExternalUrl = defaultUploadExternalUrl;
if (!StringUtils.isEmpty(params.get(ApiParams.UPLOAD_EXTERNAL_URL))) {
uploadExternalUrl = params.get(ApiParams.UPLOAD_EXTERNAL_URL);
String presentationUploadExternalUrl = defaultPresentationUploadExternalUrl;
if (!StringUtils.isEmpty(params.get(ApiParams.PRESENTATION_UPLOAD_EXTERNAL_URL))) {
presentationUploadExternalUrl = params.get(ApiParams.PRESENTATION_UPLOAD_EXTERNAL_URL);
}
String meetingLayout = defaultMeetingLayout;
@ -667,7 +687,7 @@ public class ParamsProcessorUtil {
String parentMeetingId = "";
if (isBreakout) {
internalMeetingId = params.get(ApiParams.MEETING_ID);
parentMeetingId = params.get(ApiParams.PARENT_MEETING_ID);
parentMeetingId = ServiceUtils.findMeetingFromMeetingID(params.get(ApiParams.PARENT_MEETING_ID)).getInternalId();
// We rebuild the the external meeting using the has of the parent
// meeting, the shared timestamp and the sequence number
String timeStamp = StringUtils.substringAfter(internalMeetingId, "-");
@ -680,6 +700,11 @@ public class ParamsProcessorUtil {
int html5InstanceId = processHtml5InstanceId(params.get(ApiParams.HTML5_INSTANCE_ID));
if(defaultAllowDuplicateExtUserid == false) {
log.warn("[DEPRECATION] use `maxUserConcurrentAccesses=1` instead of `allowDuplicateExtUserid=false`");
maxUserConcurrentAccesses = 1;
}
// Create the meeting with all passed in parameters.
Meeting meeting = new Meeting.Builder(externalMeetingId,
internalMeetingId, createTime).withName(meetingName)
@ -706,15 +731,15 @@ public class ParamsProcessorUtil {
.withMeetingLayout(meetingLayout)
.withBreakoutRoomsParams(breakoutParams)
.withLockSettingsParams(lockSettingsParams)
.withAllowDuplicateExtUserid(defaultAllowDuplicateExtUserid)
.withMaxUserConcurrentAccesses(maxUserConcurrentAccesses)
.withHTML5InstanceId(html5InstanceId)
.withLearningDashboardCleanupDelayInMinutes(learningDashboardCleanupMins)
.withLearningDashboardAccessToken(learningDashboardAccessToken)
.withGroups(groups)
.withDisabledFeatures(listOfDisabledFeatures)
.withNotifyRecordingIsOn(notifyRecordingIsOn)
.withUploadExternalDescription(uploadExternalDescription)
.withUploadExternalUrl(uploadExternalUrl)
.withPresentationUploadExternalDescription(presentationUploadExternalDescription)
.withPresentationUploadExternalUrl(presentationUploadExternalUrl)
.build();
if (!StringUtils.isEmpty(params.get(ApiParams.MODERATOR_ONLY_MESSAGE))) {
@ -742,6 +767,8 @@ public class ParamsProcessorUtil {
if (isBreakout) {
meeting.setSequence(Integer.parseInt(params.get(ApiParams.SEQUENCE)));
meeting.setFreeJoin(Boolean.parseBoolean(params.get(ApiParams.FREE_JOIN)));
meeting.setCaptureSlides(Boolean.parseBoolean(params.get(ApiParams.BREAKOUT_ROOMS_CAPTURE_SLIDES)));
meeting.setCaptureNotes(Boolean.parseBoolean(params.get(ApiParams.BREAKOUT_ROOMS_CAPTURE_NOTES)));
meeting.setParentMeetingId(parentMeetingId);
}
@ -820,6 +847,14 @@ public class ParamsProcessorUtil {
return allowRequestsWithoutSession;
}
public Integer getDefaultHttpSessionTimeout() {
return defaultHttpSessionTimeout;
}
public void setDefaultHttpSessionTimeout(Integer value) {
this.defaultHttpSessionTimeout = value;
}
public String getDefaultLogoutUrl() {
if ((StringUtils.isEmpty(defaultLogoutUrl)) || "default".equalsIgnoreCase(defaultLogoutUrl)) {
return defaultServerUrl;
@ -978,11 +1013,39 @@ public class ParamsProcessorUtil {
log.info("CHECKSUM={} length={}", checksum, checksum.length());
String data = apiCall + queryString + securitySalt;
String cs = DigestUtils.sha1Hex(data);
if (checksum.length() == 64) {
cs = DigestUtils.sha256Hex(data);
log.info("SHA256 {}", cs);
}
int checksumLength = checksum.length();
String cs = null;
switch(checksumLength) {
case 40:
if(supportedChecksumAlgorithms.contains("sha1")) {
cs = DigestUtils.sha1Hex(data);
log.info("SHA1 {}", cs);
}
break;
case 64:
if(supportedChecksumAlgorithms.contains("sha256")) {
cs = DigestUtils.sha256Hex(data);
log.info("SHA256 {}", cs);
}
break;
case 96:
if(supportedChecksumAlgorithms.contains("sha384")) {
cs = DigestUtils.sha384Hex(data);
log.info("SHA384 {}", cs);
}
break;
case 128:
if(supportedChecksumAlgorithms.contains("sha512")) {
cs = DigestUtils.sha512Hex(data);
log.info("SHA512 {}", cs);
}
break;
default:
log.info("No algorithm could be found that matches the provided checksum length");
}
if (cs == null || !cs.equals(checksum)) {
log.info("query string after checksum removed: [{}]", queryString);
log.info("checksumError: query string checksum failed. our: [{}], client: [{}]", cs, checksum);
@ -1068,6 +1131,10 @@ public class ParamsProcessorUtil {
this.securitySalt = securitySalt;
}
public void setSupportedChecksumAlgorithms(String supportedChecksumAlgorithms) { this.supportedChecksumAlgorithms = supportedChecksumAlgorithms; }
public void setChecksumHash(String checksumHash) { this.checksumHash = checksumHash; }
public void setDefaultMaxUsers(int defaultMaxUsers) {
this.defaultMaxUsers = defaultMaxUsers;
}
@ -1367,6 +1434,10 @@ public class ParamsProcessorUtil {
this.defaultAllowDuplicateExtUserid = allow;
}
public void setMaxUserConcurrentAccesses(Integer maxUserConcurrentAccesses) {
this.maxUserConcurrentAccesses = maxUserConcurrentAccesses;
}
public void setEndWhenNoModerator(Boolean val) {
this.defaultEndWhenNoModerator = val;
}
@ -1383,12 +1454,12 @@ public class ParamsProcessorUtil {
this.defaultNotifyRecordingIsOn = notifyRecordingIsOn;
}
public void setUploadExternalDescription(String uploadExternalDescription) {
this.defaultUploadExternalDescription = uploadExternalDescription;
public void setPresentationUploadExternalDescription(String presentationUploadExternalDescription) {
this.defaultPresentationUploadExternalDescription = presentationUploadExternalDescription;
}
public void setUploadExternalUrl(String uploadExternalUrl) {
this.defaultUploadExternalUrl = uploadExternalUrl;
public void setPresentationUploadExternalUrl(String presentationUploadExternalUrl) {
this.defaultPresentationUploadExternalUrl = presentationUploadExternalUrl;
}
public void setBbbVersion(String version) {

View File

@ -3,9 +3,13 @@ package org.bigbluebutton.api.domain;
public class BreakoutRoomsParams {
public final Boolean record;
public final Boolean privateChatEnabled;
public final Boolean captureNotes;
public final Boolean captureSlides;
public BreakoutRoomsParams(Boolean record, Boolean privateChatEnabled) {
public BreakoutRoomsParams(Boolean record, Boolean privateChatEnabled, Boolean captureNotes, Boolean captureSlides) {
this.record = record;
this.privateChatEnabled = privateChatEnabled;
this.captureNotes = captureNotes;
this.captureSlides = captureSlides;
}
}

View File

@ -42,7 +42,9 @@ public class Meeting {
private String parentMeetingId = "bbb-none"; // Initialize so we don't send null in the json message.
private Integer sequence = 0;
private Boolean freeJoin = false;
private Integer duration = 0;
private Boolean captureSlides = false;
private Boolean captureNotes = false;
private Integer duration = 0;
private long createdTime = 0;
private long startTime = 0;
private long endTime = 0;
@ -95,8 +97,8 @@ public class Meeting {
private Boolean allowRequestsWithoutSession = false;
private Boolean allowModsToEjectCameras = false;
private Boolean meetingKeepEvents;
private String uploadExternalDescription;
private String uploadExternalUrl;
private String presentationUploadExternalDescription;
private String presentationUploadExternalUrl;
private Integer meetingExpireIfNoUserJoinedInMinutes = 5;
private Integer meetingExpireWhenLastUserLeftInMinutes = 1;
@ -109,7 +111,7 @@ public class Meeting {
public final BreakoutRoomsParams breakoutRoomsParams;
public final LockSettingsParams lockSettingsParams;
public final Boolean allowDuplicateExtUserid;
public final Integer maxUserConcurrentAccesses;
private String meetingEndedCallbackURL = "";
@ -121,8 +123,8 @@ public class Meeting {
intMeetingId = builder.internalId;
disabledFeatures = builder.disabledFeatures;
notifyRecordingIsOn = builder.notifyRecordingIsOn;
uploadExternalDescription = builder.uploadExternalDescription;
uploadExternalUrl = builder.uploadExternalUrl;
presentationUploadExternalDescription = builder.presentationUploadExternalDescription;
presentationUploadExternalUrl = builder.presentationUploadExternalUrl;
if (builder.viewerPass == null){
viewerPass = "";
} else {
@ -163,7 +165,7 @@ public class Meeting {
allowRequestsWithoutSession = builder.allowRequestsWithoutSession;
breakoutRoomsParams = builder.breakoutRoomsParams;
lockSettingsParams = builder.lockSettingsParams;
allowDuplicateExtUserid = builder.allowDuplicateExtUserid;
maxUserConcurrentAccesses = builder.maxUserConcurrentAccesses;
endWhenNoModerator = builder.endWhenNoModerator;
endWhenNoModeratorDelayInMinutes = builder.endWhenNoModeratorDelayInMinutes;
html5InstanceId = builder.html5InstanceId;
@ -197,6 +199,28 @@ public class Meeting {
return users;
}
public Integer countUniqueExtIds() {
List<String> uniqueExtIds = new ArrayList<String>();
for (User user : users.values()) {
if(!uniqueExtIds.contains(user.getExternalUserId())) {
uniqueExtIds.add(user.getExternalUserId());
}
}
return uniqueExtIds.size();
}
public List<String> getUsersWithExtId(String externalUserId) {
List<String> usersWithExtId = new ArrayList<String>();
for (User user : users.values()) {
if(user.getExternalUserId().equals(externalUserId)) {
usersWithExtId.add(user.getInternalUserId());
}
}
return usersWithExtId;
}
public void guestIsWaiting(String userId) {
RegisteredUser ruser = registeredUsers.get(userId);
if (ruser != null) {
@ -288,6 +312,22 @@ public class Meeting {
this.freeJoin = freeJoin;
}
public Boolean isCaptureSlides() {
return captureSlides;
}
public void setCaptureSlides(Boolean capture) {
this.captureSlides = captureSlides;
}
public Boolean isCaptureNotes() {
return captureNotes;
}
public void setCaptureNotes(Boolean capture) {
this.captureNotes = captureNotes;
}
public Integer getDuration() {
return duration;
}
@ -384,11 +424,11 @@ public class Meeting {
return notifyRecordingIsOn;
}
public String getUploadExternalDescription() {
return uploadExternalDescription;
public String getPresentationUploadExternalDescription() {
return presentationUploadExternalDescription;
}
public String getUploadExternalUrl() {
return uploadExternalUrl;
public String getPresentationUploadExternalUrl() {
return presentationUploadExternalUrl;
}
public String getWelcomeMessageTemplate() {
@ -504,6 +544,10 @@ public class Meeting {
return maxUsers;
}
public Integer getMaxUserConcurrentAccesses() {
return maxUserConcurrentAccesses;
}
public int getLogoutTimer() {
return logoutTimer;
}
@ -633,21 +677,19 @@ public class Meeting {
return this.users.get(id);
}
public User getUserWithExternalId(String externalUserId) {
for (Map.Entry<String, User> entry : users.entrySet()) {
User u = entry.getValue();
if (u.getExternalUserId().equals(externalUserId)) {
return u;
}
}
return null;
}
public int getNumUsers(){
return this.users.size();
}
public int getNumUsersOnline(){
int countUsersOnline = 0;
for (User user : this.users.values()) {
if(!user.hasLeft()) countUsersOnline++;
}
return countUsersOnline;
}
public int getNumModerators() {
int sum = 0;
for (Map.Entry<String, User> entry : users.entrySet()) {
@ -821,8 +863,8 @@ public class Meeting {
private String learningDashboardAccessToken;
private ArrayList<String> disabledFeatures;
private Boolean notifyRecordingIsOn;
private String uploadExternalDescription;
private String uploadExternalUrl;
private String presentationUploadExternalDescription;
private String presentationUploadExternalUrl;
private int duration;
private String webVoice;
private String telVoice;
@ -843,7 +885,8 @@ public class Meeting {
private String meetingLayout;
private BreakoutRoomsParams breakoutRoomsParams;
private LockSettingsParams lockSettingsParams;
private Boolean allowDuplicateExtUserid;
private Integer maxUserConcurrentAccesses;
private Boolean endWhenNoModerator;
private Integer endWhenNoModeratorDelayInMinutes;
private int html5InstanceId;
@ -950,13 +993,13 @@ public class Meeting {
return this;
}
public Builder withUploadExternalDescription(String d) {
this.uploadExternalDescription = d;
public Builder withPresentationUploadExternalDescription(String d) {
this.presentationUploadExternalDescription = d;
return this;
}
public Builder withUploadExternalUrl(String u) {
this.uploadExternalUrl = u;
public Builder withPresentationUploadExternalUrl(String u) {
this.presentationUploadExternalUrl = u;
return this;
}
@ -1035,8 +1078,8 @@ public class Meeting {
return this;
}
public Builder withAllowDuplicateExtUserid(Boolean allowDuplicateExtUserid) {
this.allowDuplicateExtUserid = allowDuplicateExtUserid;
public Builder withMaxUserConcurrentAccesses(Integer maxUserConcurrentAccesses) {
this.maxUserConcurrentAccesses = maxUserConcurrentAccesses;
return this;
}

View File

@ -25,8 +25,8 @@ public class CreateMeetingMessage {
public final String learningDashboardAccessToken;
public final ArrayList<String> disabledFeatures;
public final Boolean notifyRecordingIsOn;
public final String uploadExternalDescription;
public final String uploadExternalUrl;
public final String presentationUploadExternalDescription;
public final String presentationUploadExternalUrl;
public final Long createTime;
public final String createDate;
public final Map<String, String> metadata;
@ -38,8 +38,8 @@ public class CreateMeetingMessage {
String viewerPass, String learningDashboardAccessToken,
ArrayList<String> disabledFeatures,
Boolean notifyRecordingIsOn,
String uploadExternalDescription,
String uploadExternalUrl,
String presentationUploadExternalDescription,
String presentationUploadExternalUrl,
Long createTime, String createDate, Map<String, String> metadata) {
this.id = id;
this.externalId = externalId;
@ -58,8 +58,8 @@ public class CreateMeetingMessage {
this.learningDashboardAccessToken = learningDashboardAccessToken;
this.disabledFeatures = disabledFeatures;
this.notifyRecordingIsOn = notifyRecordingIsOn;
this.uploadExternalDescription = uploadExternalDescription;
this.uploadExternalUrl = uploadExternalUrl;
this.presentationUploadExternalDescription = presentationUploadExternalDescription;
this.presentationUploadExternalUrl = presentationUploadExternalUrl;
this.createTime = createTime;
this.createDate = createDate;
this.metadata = metadata;

View File

@ -19,6 +19,8 @@ public class CreateBreakoutRoom implements IMessage {
public final Integer sourcePresentationSlide;
public final Boolean record;
public final Boolean privateChatEnabled;
public final Boolean captureNotes; // Upload shared notes to main room after breakout room end
public final Boolean captureSlides; // Upload annotated breakout slides to main room after breakout room end
public CreateBreakoutRoom(String meetingId,
String parentMeetingId,
@ -35,7 +37,9 @@ public class CreateBreakoutRoom implements IMessage {
String sourcePresentationId,
Integer sourcePresentationSlide,
Boolean record,
Boolean privateChatEnabled) {
Boolean privateChatEnabled,
Boolean captureNotes,
Boolean captureSlides) {
this.meetingId = meetingId;
this.parentMeetingId = parentMeetingId;
this.name = name;
@ -52,5 +56,7 @@ public class CreateBreakoutRoom implements IMessage {
this.sourcePresentationSlide = sourcePresentationSlide;
this.record = record;
this.privateChatEnabled = privateChatEnabled;
this.captureNotes = captureNotes;
this.captureSlides = captureSlides;
}
}

View File

@ -1,22 +0,0 @@
package org.bigbluebutton.api.model.constraint;
import org.bigbluebutton.api.model.validator.MaxParticipantsValidator;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Constraint(validatedBy = { MaxParticipantsValidator.class })
@Target(FIELD)
@Retention(RUNTIME)
public @interface MaxParticipantsConstraint {
String key() default "maxParticipantsReached";
String message() default "The maximum number of participants for the meeting has been reached";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}

View File

@ -0,0 +1,39 @@
package org.bigbluebutton.api.model.request;
import org.bigbluebutton.api.model.constraint.UserSessionConstraint;
import java.util.Map;
public class GetJoinUrl implements Request<GetJoinUrl.Params> {
public enum Params implements RequestParameters {
SESSION_TOKEN("sessionToken");
private final String value;
Params(String value) { this.value = value; }
public String getValue() { return value; }
}
@UserSessionConstraint
private String sessionToken;
public String getSessionToken() {
return sessionToken;
}
public void setSessionToken(String sessionToken) {
this.sessionToken = sessionToken;
}
@Override
public void populateFromParamsMap(Map<String, String[]> params) {
if(params.containsKey(GetJoinUrl.Params.SESSION_TOKEN.getValue())) setSessionToken(params.get(GetJoinUrl.Params.SESSION_TOKEN.getValue())[0]);
}
@Override
public void convertParamsFromString() {
}
}

View File

@ -1,6 +1,5 @@
package org.bigbluebutton.api.model.request;
import org.bigbluebutton.api.model.constraint.MaxParticipantsConstraint;
import org.bigbluebutton.api.model.constraint.MeetingEndedConstraint;
import org.bigbluebutton.api.model.constraint.MeetingExistsConstraint;
import org.bigbluebutton.api.model.constraint.UserSessionConstraint;

View File

@ -1,58 +0,0 @@
package org.bigbluebutton.api.model.request;
import org.bigbluebutton.api.model.constraint.MeetingExistsConstraint;
import org.bigbluebutton.api.model.constraint.MeetingIDConstraint;
import org.bigbluebutton.api.model.constraint.NotEmpty;
import org.bigbluebutton.api.model.shared.Checksum;
import java.util.Map;
public class SetPollXML extends RequestWithChecksum<SetPollXML.Params> {
public enum Params implements RequestParameters {
MEETING_ID("meetingID"),
POLL_XML("pollXML");
private final String value;
Params(String value) { this.value = value; }
public String getValue() { return value; }
}
@MeetingIDConstraint
@MeetingExistsConstraint(key = "invalidMeetingIdentifier")
private String meetingID;
@NotEmpty(key = "configXMLError", message = "You did not pass a poll XML")
private String pollXML;
public SetPollXML(Checksum checksum) {
super(checksum);
}
public String getMeetingID() {
return meetingID;
}
public void setMeetingID(String meetingID) {
this.meetingID = meetingID;
}
public String getPollXML() {
return pollXML;
}
public void setPollXML(String pollXML) {
this.pollXML = pollXML;
}
@Override
public void populateFromParamsMap(Map<String, String[]> params) {
if(params.containsKey(Params.MEETING_ID.getValue())) {
setMeetingID(params.get(Params.MEETING_ID.getValue())[0]);
}
if(params.containsKey(Params.POLL_XML.getValue())) setPollXML(params.get(Params.POLL_XML.getValue())[0]);
}
}

View File

@ -20,6 +20,7 @@ public class GetChecksumValidator implements ConstraintValidator<GetChecksumCons
@Override
public boolean isValid(GetChecksum checksum, ConstraintValidatorContext context) {
String securitySalt = ServiceUtils.getValidationService().getSecuritySalt();
String supportedChecksumAlgorithms = ServiceUtils.getValidationService().getSupportedChecksumAlgorithms();
if (securitySalt.isEmpty()) {
log.warn("Security is disabled in this service. Make sure this is intentional.");
@ -41,11 +42,37 @@ public class GetChecksumValidator implements ConstraintValidator<GetChecksumCons
}
String data = checksum.getApiCall() + queryStringWithoutChecksum + securitySalt;
String createdCheckSum = DigestUtils.sha1Hex(data);
if (providedChecksum.length() == 64) {
createdCheckSum = DigestUtils.sha256Hex(data);
log.info("SHA256 {}", createdCheckSum);
int checksumLength = providedChecksum.length();
String createdCheckSum = null;
switch(checksumLength) {
case 40:
if(supportedChecksumAlgorithms.contains("sha1")) {
createdCheckSum = DigestUtils.sha1Hex(data);
log.info("SHA1 {}", createdCheckSum);
}
break;
case 64:
if(supportedChecksumAlgorithms.contains("sha256")) {
createdCheckSum = DigestUtils.sha256Hex(data);
log.info("SHA256 {}", createdCheckSum);
}
break;
case 96:
if(supportedChecksumAlgorithms.contains("sha384")) {
createdCheckSum = DigestUtils.sha384Hex(data);
log.info("SHA384 {}", createdCheckSum);
}
break;
case 128:
if(supportedChecksumAlgorithms.contains("sha512")) {
createdCheckSum = DigestUtils.sha512Hex(data);
log.info("SHA512 {}", createdCheckSum);
}
break;
default:
log.info("No algorithm could be found that matches the provided checksum length");
}
if (createdCheckSum == null || !createdCheckSum.equals(providedChecksum)) {

View File

@ -1,50 +0,0 @@
package org.bigbluebutton.api.model.validator;
import org.bigbluebutton.api.MeetingService;
import org.bigbluebutton.api.domain.Meeting;
import org.bigbluebutton.api.domain.UserSession;
import org.bigbluebutton.api.model.constraint.MaxParticipantsConstraint;
import org.bigbluebutton.api.service.ServiceUtils;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class MaxParticipantsValidator implements ConstraintValidator<MaxParticipantsConstraint, String> {
@Override
public void initialize(MaxParticipantsConstraint constraintAnnotation) {}
@Override
public boolean isValid(String sessionToken, ConstraintValidatorContext constraintValidatorContext) {
if(sessionToken == null) {
return false;
}
MeetingService meetingService = ServiceUtils.getMeetingService();
UserSession userSession = meetingService.getUserSessionWithAuthToken(sessionToken);
if(userSession == null) {
return false;
}
Meeting meeting = meetingService.getMeeting(userSession.meetingID);
if(meeting == null) {
return false;
}
int maxParticipants = meeting.getMaxUsers();
boolean enabled = maxParticipants > 0;
boolean rejoin = meeting.getUserById(userSession.internalUserId) != null;
boolean reenter = meeting.getEnteredUserById(userSession.internalUserId) != null;
int joinedUsers = meeting.getUsers().size();
boolean reachedMax = joinedUsers >= maxParticipants;
if(enabled && !rejoin && !reenter && reachedMax) {
return false;
}
return true;
}
}

View File

@ -35,12 +35,12 @@ public class ValidationService {
GET_MEETING_INFO("getMeetingInfo", RequestType.GET),
GET_MEETINGS("getMeetings", RequestType.GET),
GET_SESSIONS("getSessions", RequestType.GET),
SET_POLL_XML("setPollXML", RequestType.POST),
GUEST_WAIT("guestWait", RequestType.GET),
ENTER("enter", RequestType.GET),
STUNS("stuns", RequestType.GET),
SIGN_OUT("signOut", RequestType.GET),
LEARNING_DASHBOARD("learningDashboard", RequestType.GET),
GET_JOIN_URL("getJoinUrl", RequestType.GET),
INSERT_DOCUMENT("insertDocument", RequestType.GET);
private final String name;
@ -56,6 +56,7 @@ public class ValidationService {
}
private String securitySalt;
private String supportedChecksumAlgorithms;
private Boolean allowRequestsWithoutSession;
private ValidatorFactory validatorFactory;
@ -68,7 +69,6 @@ public class ValidationService {
public Map<String, String> validate(ApiCall apiCall, Map<String, String[]> params, String queryString) {
log.info("Validating {} request with query string {}", apiCall.getName(), queryString);
params = sanitizeParams(params);
Request request = initializeRequest(apiCall, params, queryString);
@ -116,9 +116,6 @@ public class ValidationService {
case GET_MEETING_INFO:
request = new MeetingInfo(checksum);
break;
case SET_POLL_XML:
request = new SetPollXML(checksum);
break;
case GET_MEETINGS:
case GET_SESSIONS:
request = new SimpleRequest(checksum);
@ -141,12 +138,9 @@ public class ValidationService {
case LEARNING_DASHBOARD:
request = new LearningDashboard();
break;
}
case POST:
checksum = new PostChecksum(apiCall.getName(), checksumValue, params);
switch(apiCall) {
case SET_POLL_XML:
request = new SetPollXML(checksum);
case GET_JOIN_URL:
request = new GetJoinUrl();
break;
}
}
@ -277,6 +271,9 @@ public class ValidationService {
public void setSecuritySalt(String securitySalt) { this.securitySalt = securitySalt; }
public String getSecuritySalt() { return securitySalt; }
public void setSupportedChecksumAlgorithms(String supportedChecksumAlgorithms) { this.supportedChecksumAlgorithms = supportedChecksumAlgorithms; }
public String getSupportedChecksumAlgorithms() { return supportedChecksumAlgorithms; }
public void setAllowRequestsWithoutSession(Boolean allowRequestsWithoutSession) {
this.allowRequestsWithoutSession = allowRequestsWithoutSession;
}

View File

@ -18,7 +18,7 @@ public class ParamsUtil {
public static final String INVALID_CHARS = ",";
public static String stripControlChars(String text) {
return text.replaceAll("\\p{Cc}", "");
return text.replaceAll("\\p{Cc}", "").trim();
}
public static String escapeHTMLTags(String value) {

View File

@ -1,7 +1,6 @@
package org.bigbluebutton.api2;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.bigbluebutton.api.domain.BreakoutRoomsParams;
@ -26,7 +25,7 @@ public interface IBbbWebApiGWApp {
String moderatorPass, String viewerPass, String learningDashboardAccessToken, Long createTime,
String createDate, Boolean isBreakout, Integer sequence, Boolean freejoin, Map<String, String> metadata,
String guestPolicy, Boolean authenticatedGuest, String meetingLayout, String welcomeMsgTemplate, String welcomeMsg, String modOnlyMessage,
String dialNumber, Integer maxUsers,
String dialNumber, Integer maxUsers, Integer maxUserConcurrentAccesses,
Integer meetingExpireIfNoUserJoinedInMinutes,
Integer meetingExpireWhenLastUserLeftInMinutes,
Integer userInactivityInspectTimerInMinutes,
@ -44,14 +43,12 @@ public interface IBbbWebApiGWApp {
ArrayList<Group> groups,
ArrayList<String> disabledFeatures,
Boolean notifyRecordingIsOn,
String uploadExternalDescription,
String uploadExternalUrl);
String presentationUploadExternalDescription,
String presentationUploadExternalUrl);
void registerUser(String meetingID, String internalUserId, String fullname, String role,
String externUserID, String authToken, String avatarURL,
Boolean guest, Boolean authed, String guestStatus, Boolean excludeFromDashboard);
void ejectDuplicateUser(String meetingID, String internalUserId, String fullname,
String externUserID);
void guestWaitingLeft(String meetingID, String internalUserId);
void destroyMeeting(DestroyMeetingMessage msg);

View File

@ -38,6 +38,7 @@ public class ConversionMessageConstants {
public static final String GENERATED_SVGIMAGES_KEY = "GENERATED_SVGIMAGES";
public static final String CONVERSION_STARTED_KEY = "CONVERSION_STARTED_KEY";
public static final String CONVERSION_COMPLETED_KEY = "CONVERSION_COMPLETED";
public static final String CONVERSION_TIMEOUT_KEY = "CONVERSION_TIMEOUT";
private ConversionMessageConstants() {
throw new IllegalStateException("ConversionMessageConstants is a utility class. Instanciation is forbidden.");

View File

@ -94,7 +94,6 @@ public class ConversionUpdateMessage {
Map<String, String> page = new HashMap<String, String>();
page.put("num", Integer.toString(i));
page.put("thumb", basePresUrl + "/thumbnail/" + i);
page.put("swf", basePresUrl + "/slide/" + i);
page.put("text", basePresUrl + "/textfiles/" + i);
pages.add(page);

View File

@ -21,4 +21,5 @@ package org.bigbluebutton.presentation;
public interface DocumentConversionService {
void processDocument(UploadedPresentation pres);
void sendDocConversionFailedOnMimeType(UploadedPresentation pres, String fileMime, String fileExtension);
}

View File

@ -19,21 +19,25 @@
package org.bigbluebutton.presentation;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import org.bigbluebutton.api2.IBbbWebApiGWApp;
import org.bigbluebutton.presentation.imp.*;
import org.bigbluebutton.presentation.messages.DocConversionRequestReceived;
import org.bigbluebutton.presentation.messages.DocInvalidMimeType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
import static org.bigbluebutton.presentation.Util.deleteDirectoryFromFileHandlingErrors;
public class DocumentConversionServiceImp implements DocumentConversionService {
private static Logger log = LoggerFactory.getLogger(DocumentConversionServiceImp.class);
private IBbbWebApiGWApp gw;
private OfficeToPdfConversionService officeToPdfConversionService;
private SwfSlidesGenerationProgressNotifier notifier;
private SlidesGenerationProgressNotifier notifier;
private PresentationFileProcessor presentationFileProcessor;
@ -93,6 +97,9 @@ public class DocumentConversionServiceImp implements DocumentConversionService {
}
} else {
File presentationFile = pres.getUploadedFile();
deleteDirectoryFromFileHandlingErrors(presentationFile);
Map<String, Object> logData = new HashMap<String, Object>();
logData = new HashMap<String, Object>();
logData.put("podId", pres.getPodId());
@ -124,6 +131,11 @@ public class DocumentConversionServiceImp implements DocumentConversionService {
}
}
public void sendDocConversionFailedOnMimeType(UploadedPresentation pres, String fileMime,
String fileExtension) {
notifier.sendInvalidMimeTypeMessage(pres, fileMime, fileExtension);
}
private void sendDocConversionRequestReceived(UploadedPresentation pres) {
if (! pres.isConversionStarted()) {
Map<String, Object> logData = new HashMap<String, Object>();
@ -166,7 +178,7 @@ public class DocumentConversionServiceImp implements DocumentConversionService {
officeToPdfConversionService = s;
}
public void setSwfSlidesGenerationProgressNotifier(SwfSlidesGenerationProgressNotifier notifier) {
public void setSlidesGenerationProgressNotifier(SlidesGenerationProgressNotifier notifier) {
this.notifier = notifier;
}

View File

@ -39,5 +39,6 @@ public final class FileTypeConstants {
public static final String JPG = "jpg";
public static final String JPEG = "jpeg";
public static final String PNG = "png";
public static final String SVG = "svg";
private FileTypeConstants() {} // Prevent instantiation
}

View File

@ -1,94 +0,0 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.presentation;
import java.io.File;
import java.io.IOException;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ImageToSwfSlide {
private static Logger log = LoggerFactory.getLogger(ImageToSwfSlide.class);
private UploadedPresentation pres;
private int page;
private PageConverter imageToSwfConverter;
private String BLANK_SLIDE;
private boolean done = false;
private File slide;
public ImageToSwfSlide(UploadedPresentation pres, int page) {
this.pres = pres;
this.page = page;
}
public ImageToSwfSlide createSlide() {
File presentationFile = pres.getUploadedFile();
slide = new File(presentationFile.getParent() + File.separatorChar + "slide-" + page + ".swf");
log.debug("Creating slide {}", slide.getAbsolutePath());
imageToSwfConverter.convert(presentationFile, slide, page, pres);
// If all fails, generate a blank slide.
if (!slide.exists()) {
log.warn("Creating blank slide for {}", slide.getAbsolutePath());
generateBlankSlide();
}
done = true;
return this;
}
public void generateBlankSlide() {
if (BLANK_SLIDE != null) {
copyBlankSlide(slide);
} else {
log.error("Blank slide has not been set");
}
}
private void copyBlankSlide(File slide) {
try {
FileUtils.copyFile(new File(BLANK_SLIDE), slide);
} catch (IOException e) {
log.error("IOException while copying blank slide.", e);
}
}
public void setPageConverter(PageConverter converter) {
this.imageToSwfConverter = converter;
}
public void setBlankSlide(String blankSlide) {
this.BLANK_SLIDE = blankSlide;
}
public boolean isDone() {
return done;
}
public int getPageNumber() {
return page;
}
}

View File

@ -0,0 +1,67 @@
package org.bigbluebutton.presentation;
import java.util.*;
import static org.bigbluebutton.presentation.FileTypeConstants.*;
public class MimeTypeUtils {
private static final String XLS = "application/vnd.ms-excel";
private static final String XLSX = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
private static final String DOC = "application/msword";
private static final String DOCX = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
private static final String PPT = "application/vnd.ms-powerpoint";
private static final String PPTX = "application/vnd.openxmlformats-officedocument.presentationml.presentation";
private static final String ODT = "application/vnd.oasis.opendocument.text";
private static final String RTF = "application/rtf";
private static final String TXT = "text/plain";
private static final String ODS = "application/vnd.oasis.opendocument.spreadsheet";
private static final String ODP = "application/vnd.oasis.opendocument.presentation";
private static final String PDF = "application/pdf";
private static final String JPEG = "image/jpeg";
private static final String PNG = "image/png";
private static final String SVG = "image/svg+xml";
private static final HashMap<String,String> EXTENSIONS_MIME = new HashMap<String,String>(16) {
{
// Add all the supported files
put(FileTypeConstants.XLS, XLS);
put(FileTypeConstants.XLSX, XLSX);
put(FileTypeConstants.DOC, DOC);
put(FileTypeConstants.DOCX, DOCX);
put(FileTypeConstants.PPT, PPT);
put(FileTypeConstants.PPTX, PPTX);
put(FileTypeConstants.ODT, ODT);
put(FileTypeConstants.RTF, RTF);
put(FileTypeConstants.TXT, TXT);
put(FileTypeConstants.ODS, ODS);
put(FileTypeConstants.ODP, ODP);
put(FileTypeConstants.PDF, PDF);
put(FileTypeConstants.JPG, JPEG);
put(FileTypeConstants.JPEG, JPEG);
put(FileTypeConstants.PNG, PNG);
put(FileTypeConstants.SVG, SVG);
}
};
public Boolean extensionMatchMimeType(String mimeType, String finalExtension) {
if(EXTENSIONS_MIME.containsKey(finalExtension.toLowerCase()) &&
EXTENSIONS_MIME.get(finalExtension.toLowerCase()).equalsIgnoreCase(mimeType)) {
return true;
} else if(EXTENSIONS_MIME.containsKey(finalExtension.toLowerCase() + 'x') &&
EXTENSIONS_MIME.get(finalExtension.toLowerCase() + 'x').equalsIgnoreCase(mimeType)) {
//Exception for MS Office files named with old extension but using internally the new mime type
//e.g. a file named with extension `ppt` but has the content of a `pptx`
return true;
}
return false;
}
public List<String> getValidMimeTypes() {
List<String> validMimeTypes = Arrays.asList(XLS, XLSX,
DOC, DOCX, PPT, PPTX, ODT, RTF, TXT, ODS, ODP,
PDF, JPEG, PNG, SVG
);
return validMimeTypes;
}
}

View File

@ -61,7 +61,8 @@ public class OfficeToPdfConversionSuccessFilter {
"notUsedYet",
pres.isDownloadable(),
pres.isRemovable(),
pres.getConversionStatus());
pres.getConversionStatus(),
pres.getTemporaryPresentationId());
gw.sendDocConversionMsg(progress);
}

View File

@ -1,122 +0,0 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.presentation;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
public class PdfToSwfSlide {
private static Logger log = LoggerFactory.getLogger(PdfToSwfSlide.class);
private UploadedPresentation pres;
private int page;
private PageConverter pdfToSwfConverter;
private String BLANK_SLIDE;
private int MAX_SWF_FILE_SIZE;
private volatile boolean done = false;
private File slide;
private File pageFile;
public PdfToSwfSlide(UploadedPresentation pres, int page, File pageFile) {
this.pres = pres;
this.page = page;
this.pageFile = pageFile;
}
public PdfToSwfSlide createSlide() {
slide = new File(pageFile.getParent() + File.separatorChar + "slide-" + page + ".swf");
pdfToSwfConverter.convert(pageFile, slide, page, pres);
// If all fails, generate a blank slide.
if (!slide.exists()) {
log.warn("Failed to create slide. Creating blank slide for " + slide.getAbsolutePath());
generateBlankSlide();
}
done = true;
return this;
}
public void generateBlankSlide() {
if (BLANK_SLIDE != null) {
Map<String, Object> logData = new HashMap<>();
logData.put("meetingId", pres.getMeetingId());
logData.put("presId", pres.getId());
logData.put("filename", pres.getName());
logData.put("page", page);
Gson gson = new Gson();
String logStr = gson.toJson(logData);
log.warn("Creating blank slide: data={}", logStr);
copyBlankSlide(slide);
} else {
Map<String, Object> logData = new HashMap<>();
logData.put("meetingId", pres.getMeetingId());
logData.put("presId", pres.getId());
logData.put("filename", pres.getName());
logData.put("page", page);
Gson gson = new Gson();
String logStr = gson.toJson(logData);
log.warn("Failed to create blank slide: data={}", logStr);
}
}
private void copyBlankSlide(File slide) {
try {
FileUtils.copyFile(new File(BLANK_SLIDE), slide);
} catch (IOException e) {
log.error("IOException while copying blank slide.", e);
}
}
public void setPageConverter(PageConverter converter) {
this.pdfToSwfConverter = converter;
}
public void setBlankSlide(String blankSlide) {
this.BLANK_SLIDE = blankSlide;
}
public void setMaxSwfFileSize(int size) {
this.MAX_SWF_FILE_SIZE = size;
}
public boolean isDone() {
return done;
}
public int getPageNumber() {
return page;
}
}

View File

@ -62,7 +62,7 @@ public class SupportedDocumentFilter {
DocConversionProgress progress = new DocConversionProgress(pres.getPodId(), pres.getMeetingId(),
pres.getId(), pres.getId(),
pres.getName(), "notUsedYet", "notUsedYet",
pres.isDownloadable(), pres.isRemovable(), msgKey);
pres.isDownloadable(), pres.isRemovable(), msgKey, pres.getTemporaryPresentationId());
gw.sendDocConversionMsg(progress);

View File

@ -19,15 +19,25 @@
package org.bigbluebutton.presentation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.bigbluebutton.presentation.FileTypeConstants.*;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Collections;
@SuppressWarnings("serial")
public final class SupportedFileTypes {
private static Logger log = LoggerFactory.getLogger(SupportedFileTypes.class);
private static MimeTypeUtils mimeTypeUtils = new MimeTypeUtils();
private static final List<String> SUPPORTED_FILE_LIST = Collections.unmodifiableList(new ArrayList<String>(15) {
{
// Add all the supported files
@ -76,4 +86,56 @@ public final class SupportedFileTypes {
public static boolean isImageFile(String fileExtension) {
return IMAGE_FILE_LIST.contains(fileExtension.toLowerCase());
}
/*
* It was tested native java methods to detect mimetypes, such as:
* - URLConnection.guessContentTypeFromStream(InputStream is);
* - Files.probeContentType(Path path);
* - FileNameMap fileNameMap.getContentTypeFor(String file.getName());
* - MimetypesFileTypeMap fileTypeMap.getContentType(File file);
* But none of them was as successful as the linux based command
*/
public static String detectMimeType(File pres) {
String mimeType = "";
if (pres != null && pres.isFile()){
try {
ProcessBuilder processBuilder = new ProcessBuilder();
processBuilder.command("bash", "-c", "file -b --mime-type " + pres.getAbsolutePath());
Process process = processBuilder.start();
StringBuilder output = new StringBuilder();
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
output.append(line + "\n");
}
int exitVal = process.waitFor();
if (exitVal == 0) {
mimeType = output.toString().trim();
} else {
log.error("Error while executing command {} for file {}, error: {}",
process.toString(), pres.getAbsolutePath(), process.getErrorStream());
}
} catch (IOException e) {
log.error("Could not read file [{}]", pres.getAbsolutePath(), e.getMessage());
} catch (InterruptedException e) {
log.error("Flow interrupted for file [{}]", pres.getAbsolutePath(), e.getMessage());
}
}
return mimeType;
}
public static Boolean isPresentationMimeTypeValid(File pres, String fileExtension) {
String mimeType = detectMimeType(pres);
if(mimeType == null || mimeType == "") return false;
if(!mimeTypeUtils.getValidMimeTypes().contains(mimeType)) return false;
if(!mimeTypeUtils.extensionMatchMimeType(mimeType, fileExtension)) {
log.error("File with extension [{}] doesn't match with mimeType [{}].", fileExtension, mimeType);
return false;
}
return true;
}
}

View File

@ -21,7 +21,10 @@
* @version $Id: $
*/
package org.bigbluebutton.presentation;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.TimeoutException;
public interface SvgImageCreator {
public boolean createSvgImage(UploadedPresentation pres, int page);
public boolean createSvgImage(UploadedPresentation pres, int page) throws TimeoutException;
}

View File

@ -19,10 +19,15 @@
package org.bigbluebutton.presentation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.nio.file.Path;
public final class Util {
private static Logger log = LoggerFactory.getLogger(Util.class);
public static void deleteDirectory(File directory) {
/**
* Go through each directory and check if it's not empty.
@ -40,4 +45,20 @@ public final class Util {
// Now that the directory is empty. Delete it.
directory.delete();
}
public static void deleteDirectoryFromFileHandlingErrors(File presentationFile) {
if ( presentationFile != null ){
Path presDir = presentationFile.toPath().getParent();
try {
File presFileDir = new File(presDir.toString());
if (presFileDir.exists()) {
deleteDirectory(presFileDir);
}
} catch (Exception ex) {
log.error("Error while trying to delete directory {}", presDir.toString(), ex);
}
}
}
}

View File

@ -1,110 +0,0 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2015 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.presentation.handlers;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* The default command output the anlayse looks like the following: </br>
* 20 DEBUG Using</br>
* 60 VERBOSE Updating font</br>
* 80 VERBOSE Drawing
*
*/
public class Pdf2SwfPageConverterHandler extends AbstractCommandHandler {
private static Logger log = LoggerFactory
.getLogger(Pdf2SwfPageConverterHandler.class);
private static final String PLACEMENT_OUTPUT = "DEBUG Using";
private static final String TEXT_TAG_OUTPUT = "VERBOSE Updating";
private static final String IMAGE_TAG_OUTPUT = "VERBOSE Drawing";
private static final String DIGITS_AND_WHITESPACES = "\\d+\\s";
private static final String PLACEMENT_PATTERN = DIGITS_AND_WHITESPACES + PLACEMENT_OUTPUT;
private static final String TEXT_TAG_PATTERN = DIGITS_AND_WHITESPACES + TEXT_TAG_OUTPUT;
private static final String IMAGE_TAG_PATTERN = DIGITS_AND_WHITESPACES + IMAGE_TAG_OUTPUT;
/**
*
* @return The number of PlaceObject2 tags in the generated SWF
*/
public long numberOfPlacements() {
if (stdoutContains(PLACEMENT_OUTPUT)) {
try {
String out = stdoutBuilder.toString();
Pattern r = Pattern.compile(PLACEMENT_PATTERN);
Matcher m = r.matcher(out);
m.find();
return Integer
.parseInt(m.group(0).replace(PLACEMENT_OUTPUT, "").trim());
} catch (Exception e) {
log.error("Exception counting the number of placements", e);
return 0;
}
}
return 0;
}
/**
*
* @return The number of text tags in the generated SWF.
*/
public long numberOfTextTags() {
if (stdoutContains(TEXT_TAG_OUTPUT)) {
try {
String out = stdoutBuilder.toString();
Pattern r = Pattern.compile(TEXT_TAG_PATTERN);
Matcher m = r.matcher(out);
m.find();
return Integer.parseInt(m.group(0).replace(TEXT_TAG_OUTPUT, "").trim());
} catch (Exception e) {
log.error("Exception counting the number of text tags", e);
return 0;
}
}
return 0;
}
/**
*
* @return The number of image tags in the generated SWF.
*/
public long numberOfImageTags() {
if (stdoutContains(IMAGE_TAG_OUTPUT)) {
try {
String out = stdoutBuilder.toString();
Pattern r = Pattern.compile(IMAGE_TAG_PATTERN);
Matcher m = r.matcher(out);
m.find();
return Integer
.parseInt(m.group(0).replace(IMAGE_TAG_OUTPUT, "").trim());
} catch (Exception e) {
log.error("Exception counting the number of iamge tags", e);
return 0;
}
}
return 0;
}
}

View File

@ -1,27 +0,0 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2015 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.presentation.handlers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Png2SwfPageConverterHandler extends AbstractCommandHandler {
private static Logger log = LoggerFactory.getLogger(Png2SwfPageConverterHandler.class);
}

View File

@ -0,0 +1,167 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.presentation.imp;
import java.awt.image.BufferedImage;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeoutException;
import org.bigbluebutton.presentation.ImageResizer;
import org.bigbluebutton.presentation.PngCreator;
import org.bigbluebutton.presentation.SvgImageCreator;
import org.bigbluebutton.presentation.TextFileCreator;
import org.bigbluebutton.presentation.ThumbnailCreator;
import org.bigbluebutton.presentation.UploadedPresentation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.imageio.ImageIO;
public class ImageSlidesGenerationService {
private static Logger log = LoggerFactory.getLogger(ImageSlidesGenerationService.class);
private ExecutorService executor;
private SlidesGenerationProgressNotifier notifier;
private SvgImageCreator svgImageCreator;
private ThumbnailCreator thumbnailCreator;
private TextFileCreator textFileCreator;
private PngCreator pngCreator;
private ImageResizer imageResizer;
private long maxImageWidth = 2048;
private long maxImageHeight = 1536;
private long MAX_CONVERSION_TIME = 5*60*1000L;
private boolean svgImagesRequired=true;
private boolean generatePngs;
public ImageSlidesGenerationService() {
int numThreads = Runtime.getRuntime().availableProcessors();
executor = Executors.newFixedThreadPool(numThreads);
}
public void generateSlides(UploadedPresentation pres) {
for (int page = 1; page <= pres.getNumberOfPages(); page++) {
/* adding accessibility */
createTextFiles(pres, page);
createThumbnails(pres, page);
if (svgImagesRequired) {
try {
createSvgImages(pres, page);
} catch (TimeoutException e) {
log.error("Slide {} was not converted due to TimeoutException, ending process.", page, e);
notifier.sendUploadFileTimedout(pres, page);
break;
}
}
if (generatePngs) {
createPngImages(pres, page);
}
notifier.sendConversionUpdateMessage(page, pres, page);
}
System.out.println("****** Conversion complete for " + pres.getName());
notifier.sendConversionCompletedMessage(pres);
}
private void createTextFiles(UploadedPresentation pres, int page) {
log.debug("Creating textfiles for accessibility.");
notifier.sendCreatingTextFilesUpdateMessage(pres);
textFileCreator.createTextFile(pres, page);
}
private void createThumbnails(UploadedPresentation pres, int page) {
log.debug("Creating thumbnails.");
notifier.sendCreatingThumbnailsUpdateMessage(pres);
thumbnailCreator.createThumbnail(pres, page, pres.getUploadedFile());
}
private void createSvgImages(UploadedPresentation pres, int page) throws TimeoutException{
log.debug("Creating SVG images.");
try {
BufferedImage bimg = ImageIO.read(pres.getUploadedFile());
if(bimg.getWidth() > maxImageWidth || bimg.getHeight() > maxImageHeight) {
log.info("The image exceeds max dimension allowed, it will be resized.");
resizeImage(pres, maxImageWidth + "x" + maxImageHeight);
}
} catch (Exception e) {
log.error("Exception while resizing image {}", pres.getName(), e);
}
notifier.sendCreatingSvgImagesUpdateMessage(pres);
svgImageCreator.createSvgImage(pres, page);
}
private void createPngImages(UploadedPresentation pres, int page) {
pngCreator.createPng(pres, page, pres.getUploadedFile());
}
private void resizeImage(UploadedPresentation pres, String ratio) {
imageResizer.resize(pres, ratio);
}
public void setThumbnailCreator(ThumbnailCreator thumbnailCreator) {
this.thumbnailCreator = thumbnailCreator;
}
public void setTextFileCreator(TextFileCreator textFileCreator) {
this.textFileCreator = textFileCreator;
}
public void setPngCreator(PngCreator pngCreator) {
this.pngCreator = pngCreator;
}
public void setSvgImageCreator(SvgImageCreator svgImageCreator) {
this.svgImageCreator = svgImageCreator;
}
public void setGeneratePngs(boolean generatePngs) {
this.generatePngs = generatePngs;
}
public void setSvgImagesRequired(boolean svg) {
this.svgImagesRequired = svg;
}
public void setMaxConversionTime(int minutes) {
MAX_CONVERSION_TIME = minutes * 60 * 1000L;
}
public void setSlidesGenerationProgressNotifier(SlidesGenerationProgressNotifier notifier) {
this.notifier = notifier;
}
public void setImageResizer(ImageResizer imageResizer) {
this.imageResizer = imageResizer;
}
public void setMaxImageWidth(long maxImageWidth) {
this.maxImageWidth = maxImageWidth;
}
public void setMaxImageHeight(long maxImageHeight) {
this.maxImageHeight = maxImageHeight;
}
}

View File

@ -1,258 +0,0 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.presentation.imp;
import java.text.DecimalFormat;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.bigbluebutton.presentation.FileTypeConstants;
import org.bigbluebutton.presentation.ImageResizer;
import org.bigbluebutton.presentation.ImageToSwfSlide;
import org.bigbluebutton.presentation.PageConverter;
import org.bigbluebutton.presentation.PngCreator;
import org.bigbluebutton.presentation.SvgImageCreator;
import org.bigbluebutton.presentation.TextFileCreator;
import org.bigbluebutton.presentation.ThumbnailCreator;
import org.bigbluebutton.presentation.UploadedPresentation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ImageToSwfSlidesGenerationService {
private static Logger log = LoggerFactory.getLogger(ImageToSwfSlidesGenerationService.class);
private ExecutorService executor;
private CompletionService<ImageToSwfSlide> completionService;
private SwfSlidesGenerationProgressNotifier notifier;
private PageConverter jpgToSwfConverter;
private PageConverter pngToSwfConverter;
private SvgImageCreator svgImageCreator;
private ThumbnailCreator thumbnailCreator;
private TextFileCreator textFileCreator;
private PngCreator pngCreator;
private ImageResizer imageResizer;
private Long maxImageSize;
private long MAX_CONVERSION_TIME = 5*60*1000L;
private String BLANK_SLIDE;
private boolean swfSlidesRequired;
private boolean svgImagesRequired;
private boolean generatePngs;
public ImageToSwfSlidesGenerationService() {
int numThreads = Runtime.getRuntime().availableProcessors();
executor = Executors.newFixedThreadPool(numThreads);
completionService = new ExecutorCompletionService<ImageToSwfSlide>(executor);
}
public void generateSlides(UploadedPresentation pres) {
for (int page = 1; page <= pres.getNumberOfPages(); page++) {
if (swfSlidesRequired) {
if (pres.getNumberOfPages() > 0) {
PageConverter pageConverter = determinePageConverter(pres);
convertImageToSwf(pres, pageConverter);
}
}
/* adding accessibility */
createTextFiles(pres, page);
createThumbnails(pres, page);
if (svgImagesRequired) {
createSvgImages(pres, page);
}
if (generatePngs) {
createPngImages(pres, page);
}
notifier.sendConversionUpdateMessage(page, pres, page);
}
System.out.println("****** Conversion complete for " + pres.getName());
notifier.sendConversionCompletedMessage(pres);
}
private PageConverter determinePageConverter(UploadedPresentation pres) {
String fileType = pres.getFileType().toUpperCase();
if ((FileTypeConstants.JPEG.equalsIgnoreCase(fileType)) || (FileTypeConstants.JPG.equalsIgnoreCase(fileType))) {
return jpgToSwfConverter;
}
return pngToSwfConverter;
}
private void createTextFiles(UploadedPresentation pres, int page) {
log.debug("Creating textfiles for accessibility.");
notifier.sendCreatingTextFilesUpdateMessage(pres);
textFileCreator.createTextFile(pres, page);
}
private void createThumbnails(UploadedPresentation pres, int page) {
log.debug("Creating thumbnails.");
notifier.sendCreatingThumbnailsUpdateMessage(pres);
thumbnailCreator.createThumbnail(pres, page, pres.getUploadedFile());
}
private void createSvgImages(UploadedPresentation pres, int page) {
log.debug("Creating SVG images.");
notifier.sendCreatingSvgImagesUpdateMessage(pres);
svgImageCreator.createSvgImage(pres, page);
}
private void createPngImages(UploadedPresentation pres, int page) {
pngCreator.createPng(pres, page, pres.getUploadedFile());
}
private void convertImageToSwf(UploadedPresentation pres, PageConverter pageConverter) {
int numPages = pres.getNumberOfPages();
// A better implementation is described at the link below
// https://stackoverflow.com/questions/4513648/how-to-estimate-the-size-of-jpeg-image-which-will-be-scaled-down
if (pres.getUploadedFile().length() > maxImageSize) {
DecimalFormat percentFormat= new DecimalFormat("#.##%");
// Resize the image and overwrite it
resizeImage(pres, percentFormat
.format(Double.valueOf(maxImageSize) / Double.valueOf(pres.getUploadedFile().length())));
}
ImageToSwfSlide[] slides = setupSlides(pres, numPages, pageConverter);
generateSlides(slides);
handleSlideGenerationResult(pres, slides);
}
private void resizeImage(UploadedPresentation pres, String ratio) {
imageResizer.resize(pres, ratio);
}
private void handleSlideGenerationResult(UploadedPresentation pres, ImageToSwfSlide[] slides) {
long endTime = System.currentTimeMillis() + MAX_CONVERSION_TIME;
for (int t = 0; t < slides.length; t++) {
Future<ImageToSwfSlide> future = null;
ImageToSwfSlide slide = null;
try {
long timeLeft = endTime - System.currentTimeMillis();
future = completionService.take();
slide = future.get(timeLeft, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
log.error("InterruptedException while creating slide {}", pres.getName(), e);
} catch (ExecutionException e) {
log.error("ExecutionException while creating slide {}", pres.getName(), e);
} catch (TimeoutException e) {
log.error("TimeoutException while converting {}", pres.getName(), e);
} finally {
if ((slide != null) && (! slide.isDone())){
log.warn("Creating blank slide for {}", slide.getPageNumber());
future.cancel(true);
slide.generateBlankSlide();
}
}
}
}
private ImageToSwfSlide[] setupSlides(UploadedPresentation pres, int numPages, PageConverter pageConverter) {
ImageToSwfSlide[] slides = new ImageToSwfSlide[numPages];
for (int page = 1; page <= numPages; page++) {
ImageToSwfSlide slide = new ImageToSwfSlide(pres, page);
slide.setBlankSlide(BLANK_SLIDE);
slide.setPageConverter(pageConverter);
// Array index is zero-based
slides[page-1] = slide;
}
return slides;
}
private void generateSlides(ImageToSwfSlide[] slides) {
for (int i = 0; i < slides.length; i++) {
final ImageToSwfSlide slide = slides[i];
completionService.submit(new Callable<ImageToSwfSlide>() {
public ImageToSwfSlide call() {
return slide.createSlide();
}
});
}
}
public void setJpgPageConverter(PageConverter converter) {
this.jpgToSwfConverter = converter;
}
public void setPngPageConverter(PageConverter converter) {
this.pngToSwfConverter = converter;
}
public void setBlankSlide(String blankSlide) {
this.BLANK_SLIDE = blankSlide;
}
public void setThumbnailCreator(ThumbnailCreator thumbnailCreator) {
this.thumbnailCreator = thumbnailCreator;
}
public void setTextFileCreator(TextFileCreator textFileCreator) {
this.textFileCreator = textFileCreator;
}
public void setPngCreator(PngCreator pngCreator) {
this.pngCreator = pngCreator;
}
public void setSvgImageCreator(SvgImageCreator svgImageCreator) {
this.svgImageCreator = svgImageCreator;
}
public void setGeneratePngs(boolean generatePngs) {
this.generatePngs = generatePngs;
}
public void setSwfSlidesRequired(boolean swf) {
this.swfSlidesRequired = swf;
}
public void setSvgImagesRequired(boolean svg) {
this.svgImagesRequired = svg;
}
public void setMaxConversionTime(int minutes) {
MAX_CONVERSION_TIME = minutes * 60 * 1000L;
}
public void setSwfSlidesGenerationProgressNotifier(SwfSlidesGenerationProgressNotifier notifier) {
this.notifier = notifier;
}
public void setImageResizer(ImageResizer imageResizer) {
this.imageResizer = imageResizer;
}
public void setMaxImageSize(Long maxImageSize) {
this.maxImageSize = maxImageSize;
}
}

View File

@ -1,66 +0,0 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.presentation.imp;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import org.bigbluebutton.presentation.PageConverter;
import org.bigbluebutton.presentation.UploadedPresentation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
public class Jpeg2SwfPageConverter implements PageConverter {
private static Logger log = LoggerFactory.getLogger(Jpeg2SwfPageConverter.class);
private String SWFTOOLS_DIR;
public boolean convert(File presentationFile, File output, int page, UploadedPresentation pres){
String COMMAND = SWFTOOLS_DIR + File.separatorChar + "jpeg2swf -o " + output.getAbsolutePath() + " " + presentationFile.getAbsolutePath();
boolean done = new ExternalProcessExecutor().exec(COMMAND, 10000);
if (done && output.exists()) {
return true;
} else {
Map<String, Object> logData = new HashMap<>();
logData.put("meetingId", pres.getMeetingId());
logData.put("presId", pres.getId());
logData.put("filename", pres.getName());
logData.put("logCode", "jpg_to_swf_conversion_failed");
logData.put("message", "Failed to convert: " + output.getAbsolutePath() + " does not exist.");
Gson gson = new Gson();
String logStr = gson.toJson(logData);
log.warn(" --analytics-- data={}", logStr);
return false;
}
}
public void setSwfToolsDir(String dir) {
SWFTOOLS_DIR = dir;
}
}

View File

@ -35,6 +35,8 @@ import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
import static org.bigbluebutton.presentation.Util.deleteDirectoryFromFileHandlingErrors;
public abstract class Office2PdfPageConverter {
private static Logger log = LoggerFactory.getLogger(Office2PdfPageConverter.class);
@ -95,6 +97,7 @@ public abstract class Office2PdfPageConverter {
return false;
}
} catch (Exception e) {
deleteDirectoryFromFileHandlingErrors(presentationFile);
Map<String, Object> logData = new HashMap<>();
logData.put("meetingId", pres.getMeetingId());
logData.put("presId", pres.getId());

View File

@ -3,56 +3,46 @@ package org.bigbluebutton.presentation.imp;
import org.bigbluebutton.presentation.*;
import java.io.File;
import java.util.concurrent.TimeoutException;
public class PageToConvert {
private UploadedPresentation pres;
private int page;
private boolean swfSlidesRequired;
private boolean svgImagesRequired;
private boolean svgImagesRequired=true;
private boolean generatePngs;
private PageExtractor pageExtractor;
private String BLANK_SLIDE;
private int MAX_SWF_FILE_SIZE;
private TextFileCreator textFileCreator;
private SvgImageCreator svgImageCreator;
private ThumbnailCreator thumbnailCreator;
private PngCreator pngCreator;
private PageConverter pdfToSwfConverter;
private SwfSlidesGenerationProgressNotifier notifier;
private SlidesGenerationProgressNotifier notifier;
private File pageFile;
private String messageErrorInConversion;
public PageToConvert(UploadedPresentation pres,
int page,
File pageFile,
boolean swfSlidesRequired,
boolean svgImagesRequired,
boolean generatePngs,
TextFileCreator textFileCreator,
SvgImageCreator svgImageCreator,
ThumbnailCreator thumbnailCreator,
PngCreator pngCreator,
PageConverter pdfToSwfConverter,
SwfSlidesGenerationProgressNotifier notifier,
String blankSlide,
int maxSwfFileSize) {
SlidesGenerationProgressNotifier notifier) {
this.pres = pres;
this.page = page;
this.pageFile = pageFile;
this.swfSlidesRequired = swfSlidesRequired;
this.svgImagesRequired = svgImagesRequired;
this.generatePngs = generatePngs;
this.textFileCreator = textFileCreator;
this.svgImageCreator = svgImageCreator;
this.thumbnailCreator = thumbnailCreator;
this.pngCreator = pngCreator;
this.pdfToSwfConverter = pdfToSwfConverter;
this.notifier = notifier;
this.BLANK_SLIDE = blankSlide;
this.MAX_SWF_FILE_SIZE = maxSwfFileSize;
}
public File getPageFile() {
@ -71,12 +61,15 @@ public class PageToConvert {
return pres.getMeetingId();
}
public PageToConvert convert() {
public String getMessageErrorInConversion() {
return messageErrorInConversion;
}
// Only create SWF files if the configuration requires it
if (swfSlidesRequired) {
convertPdfToSwf(pres, page, pageFile);
}
public void setMessageErrorInConversion(String messageErrorInConversion) {
this.messageErrorInConversion = messageErrorInConversion;
}
public PageToConvert convert() {
/* adding accessibility */
createThumbnails(pres, page, pageFile);
@ -85,7 +78,11 @@ public class PageToConvert {
// only create SVG images if the configuration requires it
if (svgImagesRequired) {
createSvgImages(pres, page);
try{
createSvgImages(pres, page);
} catch (TimeoutException e) {
messageErrorInConversion = e.getMessage();
}
}
// only create PNG images if the configuration requires it
@ -106,7 +103,7 @@ public class PageToConvert {
textFileCreator.createTextFile(pres, page);
}
private void createSvgImages(UploadedPresentation pres, int page) {
private void createSvgImages(UploadedPresentation pres, int page) throws TimeoutException {
//notifier.sendCreatingSvgImagesUpdateMessage(pres);
svgImageCreator.createSvgImage(pres, page);
}
@ -115,25 +112,4 @@ public class PageToConvert {
pngCreator.createPng(pres, page, pageFile);
}
private void convertPdfToSwf(UploadedPresentation pres, int page, File pageFile) {
PdfToSwfSlide slide = setupSlide(pres, page, pageFile);
generateSlides(pres, slide);
}
private void generateSlides(UploadedPresentation pres, PdfToSwfSlide slide) {
slide.createSlide();
if (!slide.isDone()) {
slide.generateBlankSlide();
}
}
private PdfToSwfSlide setupSlide(UploadedPresentation pres, int page, File pageFile) {
PdfToSwfSlide slide = new PdfToSwfSlide(pres, page, pageFile);
slide.setBlankSlide(BLANK_SLIDE);
slide.setMaxSwfFileSize(MAX_SWF_FILE_SIZE);
slide.setPageConverter(pdfToSwfConverter);
return slide;
}
}

View File

@ -1,239 +0,0 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.presentation.imp;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import org.apache.commons.io.FilenameUtils;
import org.bigbluebutton.presentation.PageConverter;
import org.bigbluebutton.presentation.UploadedPresentation;
import org.bigbluebutton.presentation.handlers.Pdf2PngPageConverterHandler;
import org.bigbluebutton.presentation.handlers.Pdf2SwfPageConverterHandler;
import org.bigbluebutton.presentation.handlers.Png2SwfPageConverterHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
import com.zaxxer.nuprocess.NuProcess;
import com.zaxxer.nuprocess.NuProcessBuilder;
public class Pdf2SwfPageConverter implements PageConverter {
private static Logger log = LoggerFactory.getLogger(Pdf2SwfPageConverter.class);
private String SWFTOOLS_DIR;
private String fontsDir;
private long placementsThreshold;
private long defineTextThreshold;
private long imageTagThreshold;
private String convTimeout = "7s";
private int WAIT_FOR_SEC = 7;
public boolean convert(File presentation, File output, int page, UploadedPresentation pres) {
long convertStart = System.currentTimeMillis();
String source = presentation.getAbsolutePath();
String dest = output.getAbsolutePath();
String AVM2SWF = "-T9";
// Building the command line wrapped in shell to be able to use shell
// feature like the pipe
NuProcessBuilder pb = new NuProcessBuilder(Arrays.asList("timeout",
convTimeout, "/bin/sh", "-c",
SWFTOOLS_DIR + File.separatorChar + "pdf2swf" + " -vv " + AVM2SWF + " -F "
+ fontsDir + " " + source + " -o "
+ dest
+ " | egrep 'shape id|Updating font|Drawing' | sed 's/ / /g' | cut -d' ' -f 1-3 | sort | uniq -cw 15"));
Pdf2SwfPageConverterHandler pHandler = new Pdf2SwfPageConverterHandler();
pb.setProcessListener(pHandler);
long pdf2SwfStart = System.currentTimeMillis();
NuProcess process = pb.start();
try {
process.waitFor(WAIT_FOR_SEC, TimeUnit.SECONDS);
} catch (InterruptedException e) {
log.error("InterruptedException while creating SWF {}", pres.getName(), e);
}
long pdf2SwfEnd = System.currentTimeMillis();
log.debug("Pdf2Swf conversion duration: {} sec", (pdf2SwfEnd - pdf2SwfStart) / 1000);
boolean timedOut = pdf2SwfEnd
- pdf2SwfStart >= Integer.parseInt(convTimeout.replaceFirst("s", ""))
* 1000;
boolean twiceTotalObjects = pHandler.numberOfPlacements()
+ pHandler.numberOfTextTags()
+ pHandler.numberOfImageTags() >= (placementsThreshold
+ defineTextThreshold + imageTagThreshold) * 2;
File destFile = new File(dest);
if (pHandler.isCommandSuccessful() && destFile.exists()
&& pHandler.numberOfPlacements() < placementsThreshold
&& pHandler.numberOfTextTags() < defineTextThreshold
&& pHandler.numberOfImageTags() < imageTagThreshold) {
return true;
} else {
// We need t delete the destination file as we are starting a new
// conversion process
if (destFile.exists()) {
destFile.delete();
}
Map<String, Object> logData = new HashMap<>();
logData.put("meetingId", pres.getMeetingId());
logData.put("presId", pres.getId());
logData.put("filename", pres.getName());
logData.put("page", page);
logData.put("convertSuccess", pHandler.isCommandSuccessful());
logData.put("fileExists", destFile.exists());
logData.put("numObjectTags", pHandler.numberOfPlacements());
logData.put("numTextTags", pHandler.numberOfTextTags());
logData.put("numImageTags", pHandler.numberOfImageTags());
logData.put("logCode", "problem_with_generated_swf");
Gson gson = new Gson();
String logStr = gson.toJson(logData);
log.warn(" --analytics-- data={}", logStr);
File tempPng = null;
String basePresentationame = UUID.randomUUID().toString();
try {
tempPng = File.createTempFile(basePresentationame + "-" + page, ".png");
} catch (IOException ioException) {
// We should never fall into this if the server is correctly configured
logData = new HashMap<>();
logData.put("meetingId", pres.getMeetingId());
logData.put("presId", pres.getId());
logData.put("filename", pres.getName());
logData.put("logCode", "failed_to_create_temp_file");
logData.put("message", "Unable to create temporary files for pdf to swf.");
gson = new Gson();
logStr = gson.toJson(logData);
log.error(" --analytics-- data={}", logStr, ioException);
}
// long pdfStart = System.currentTimeMillis();
// Step 1: Convert a PDF page to PNG using a raw pdftocairo
NuProcessBuilder pbPng = new NuProcessBuilder(
Arrays.asList("timeout", convTimeout, "pdftocairo", "-png",
"-singlefile", "-r", timedOut || twiceTotalObjects ? "72" : "150",
presentation.getAbsolutePath(), tempPng.getAbsolutePath()
.substring(0, tempPng.getAbsolutePath().lastIndexOf('.'))));
Pdf2PngPageConverterHandler pbPngHandler = new Pdf2PngPageConverterHandler();
pbPng.setProcessListener(pbPngHandler);
NuProcess processPng = pbPng.start();
try {
processPng.waitFor(WAIT_FOR_SEC, TimeUnit.SECONDS);
} catch (InterruptedException e) {
log.error("InterruptedException while creating temporary PNG {}", pres.getName(), e);
}
//long pdfEnd = System.currentTimeMillis();
//log.debug("pdftocairo conversion duration: {} sec", (pdfEnd - pdfStart) / 1000);
// long png2swfStart = System.currentTimeMillis();
// Step 2: Convert a PNG image to SWF
// We need to update the file path as pdftocairo adds "-page.png"
source = tempPng.getAbsolutePath();
NuProcessBuilder pbSwf = new NuProcessBuilder(
Arrays.asList("timeout", convTimeout,
SWFTOOLS_DIR + File.separatorChar + "png2swf", "-o", dest, source));
Png2SwfPageConverterHandler pSwfHandler = new Png2SwfPageConverterHandler();
pbSwf.setProcessListener(pSwfHandler);
NuProcess processSwf = pbSwf.start();
try {
processSwf.waitFor(WAIT_FOR_SEC, TimeUnit.SECONDS);
} catch (InterruptedException e) {
log.error("InterruptedException while creating SWF {}", pres.getName(), e);
}
//long png2swfEnd = System.currentTimeMillis();
//log.debug("SwfTools conversion duration: {} sec", (png2swfEnd - png2swfStart) / 1000);
// Delete the temporary PNG and PDF files after finishing the image
// conversion
tempPng.delete();
boolean doneSwf = pSwfHandler.isCommandSuccessful();
long convertEnd = System.currentTimeMillis();
logData = new HashMap<>();
logData.put("meetingId", pres.getMeetingId());
logData.put("presId", pres.getId());
logData.put("filename", pres.getName());
logData.put("page", page);
logData.put("conversionTime(sec)", (convertEnd - convertStart) / 1000);
logData.put("logCode", "conversion_took_too_long");
logData.put("message", "PDF to SWF conversion took a long time.");
logStr = gson.toJson(logData);
log.info(" --analytics-- data={}", logStr);
if (doneSwf && destFile.exists()) {
return true;
} else {
logData = new HashMap<>();
logData.put("meetingId", pres.getMeetingId());
logData.put("presId", pres.getId());
logData.put("filename", pres.getName());
logData.put("page", page);
logData.put("conversionTime(sec)", (convertEnd - convertStart) / 1000);
logData.put("logCode", "pdf2swf_conversion_failed");
logData.put("message", "Failed to convert: " + destFile + " does not exist.");
logStr = gson.toJson(logData);
log.warn(" --analytics-- data={}", logStr);
return false;
}
}
}
public void setSwfToolsDir(String dir) {
SWFTOOLS_DIR = dir;
}
public void setFontsDir(String dir) {
fontsDir = dir;
}
public void setPlacementsThreshold(long threshold) {
placementsThreshold = threshold;
}
public void setDefineTextThreshold(long threshold) {
defineTextThreshold = threshold;
}
public void setImageTagThreshold(long threshold) {
imageTagThreshold = threshold;
}
}

View File

@ -1,62 +1,62 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2015 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.presentation.imp;
import java.util.ArrayList;
import java.util.concurrent.*;
import org.bigbluebutton.presentation.messages.PageConvertProgressMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class PdfToSwfSlidesGenerationService {
private static Logger log = LoggerFactory.getLogger(PdfToSwfSlidesGenerationService.class);
private ExecutorService executor;
private BlockingQueue<PageToConvert> messages = new LinkedBlockingQueue<PageToConvert>();
private PresentationConversionCompletionService presentationConversionCompletionService;
public PdfToSwfSlidesGenerationService(int numConversionThreads) {
executor = Executors.newFixedThreadPool(numConversionThreads);
}
public void process(PageToConvert pageToConvert) {
Runnable task = new Runnable() {
public void run() {
pageToConvert.convert();
PageConvertProgressMessage msg = new PageConvertProgressMessage(
pageToConvert.getPageNumber(),
pageToConvert.getPresId(),
pageToConvert.getMeetingId(),
new ArrayList<>());
presentationConversionCompletionService.handle(msg);
pageToConvert.getPageFile().delete();
}
};
executor.execute(task);
}
public void setPresentationConversionCompletionService(PresentationConversionCompletionService s) {
this.presentationConversionCompletionService = s;
}
}
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2015 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.presentation.imp;
import java.util.ArrayList;
import java.util.concurrent.*;
import org.bigbluebutton.presentation.messages.PageConvertProgressMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class PdfSlidesGenerationService {
private static Logger log = LoggerFactory.getLogger(PdfSlidesGenerationService.class);
private ExecutorService executor;
private BlockingQueue<PageToConvert> messages = new LinkedBlockingQueue<PageToConvert>();
private PresentationConversionCompletionService presentationConversionCompletionService;
public PdfSlidesGenerationService(int numConversionThreads) {
executor = Executors.newFixedThreadPool(numConversionThreads);
}
public void process(PageToConvert pageToConvert) {
Runnable task = new Runnable() {
public void run() {
pageToConvert.convert();
PageConvertProgressMessage msg = new PageConvertProgressMessage(
pageToConvert.getPageNumber(),
pageToConvert.getPresId(),
pageToConvert.getMeetingId(),
new ArrayList<>());
presentationConversionCompletionService.handle(msg);
pageToConvert.getPageFile().delete();
}
};
executor.execute(task);
}
public void setPresentationConversionCompletionService(PresentationConversionCompletionService s) {
this.presentationConversionCompletionService = s;
}
}

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