Merge branch 'develop' into fixjitterbuffer
This commit is contained in:
commit
4c359d83a7
2
.github/workflows/automated-tests.yml
vendored
2
.github/workflows/automated-tests.yml
vendored
@ -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
|
||||
|
5
.github/workflows/check-merge-conflict.yml
vendored
5
.github/workflows/check-merge-conflict.yml
vendored
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
//
|
||||
|
@ -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"
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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",
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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 _ =>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -13,6 +13,7 @@ class PadsApp2x(implicit val context: ActorContext)
|
||||
with PadUpdatedSysMsgHdlr
|
||||
with PadContentSysMsgHdlr
|
||||
with PadPatchSysMsgHdlr
|
||||
with PadUpdatePubMsgHdlr {
|
||||
with PadUpdatePubMsgHdlr
|
||||
with PadPinnedReqMsgHdlr {
|
||||
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -158,7 +158,6 @@ class UsersApp(
|
||||
with SelectRandomViewerReqMsgHdlr
|
||||
with AssignPresenterReqMsgHdlr
|
||||
with ChangeUserPinStateReqMsgHdlr
|
||||
with EjectDuplicateUserReqMsgHdlr
|
||||
with EjectUserFromMeetingCmdMsgHdlr
|
||||
with EjectUserFromMeetingSysMsgHdlr
|
||||
with MuteUserCmdMsgHdlr {
|
||||
|
@ -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,
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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."
|
||||
|
@ -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] = {
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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 => {
|
||||
|
@ -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 {
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -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 =>
|
||||
|
@ -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)))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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")
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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])
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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 ------------
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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";
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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 {};
|
||||
}
|
@ -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() {
|
||||
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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]);
|
||||
}
|
||||
}
|
@ -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)) {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
@ -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.");
|
||||
|
@ -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);
|
||||
|
@ -21,4 +21,5 @@ package org.bigbluebutton.presentation;
|
||||
|
||||
public interface DocumentConversionService {
|
||||
void processDocument(UploadedPresentation pres);
|
||||
void sendDocConversionFailedOnMimeType(UploadedPresentation pres, String fileMime, String fileExtension);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -61,7 +61,8 @@ public class OfficeToPdfConversionSuccessFilter {
|
||||
"notUsedYet",
|
||||
pres.isDownloadable(),
|
||||
pres.isRemovable(),
|
||||
pres.getConversionStatus());
|
||||
pres.getConversionStatus(),
|
||||
pres.getTemporaryPresentationId());
|
||||
gw.sendDocConversionMsg(progress);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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());
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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
Loading…
Reference in New Issue
Block a user