diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/breakout/CreateBreakoutRoomsCmdMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/breakout/CreateBreakoutRoomsCmdMsgHdlr.scala index bd87ec10a0fa..950f77b0ba5a 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/breakout/CreateBreakoutRoomsCmdMsgHdlr.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/breakout/CreateBreakoutRoomsCmdMsgHdlr.scala @@ -3,6 +3,7 @@ package org.bigbluebutton.core.apps.breakout import org.bigbluebutton.common2.msgs._ import org.bigbluebutton.core.apps.{ BreakoutModel, PermissionCheck, RightsManagementTrait } import org.bigbluebutton.core.domain.{ BreakoutRoom2x, MeetingState2x } +import org.bigbluebutton.core.models.PresentationInPod import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter } import org.bigbluebutton.core.running.MeetingActor @@ -103,7 +104,7 @@ trait CreateBreakoutRoomsCmdMsgHdlr extends RightsManagementTrait { for { defaultPod <- state.presentationPodManager.getDefaultPod() curPres <- defaultPod.getCurrentPresentation() - curPage <- curPres.getCurrentPage(curPres) + curPage <- PresentationInPod.getCurrentPage(curPres) } yield { currentSlide = curPage.num } diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/presentationpod/PdfConversionInvalidErrorSysPubMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/presentationpod/PdfConversionInvalidErrorSysPubMsgHdlr.scala index c625dbde7747..2577dc3bd0f5 100644 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/presentationpod/PdfConversionInvalidErrorSysPubMsgHdlr.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/presentationpod/PdfConversionInvalidErrorSysPubMsgHdlr.scala @@ -6,7 +6,7 @@ import org.bigbluebutton.core.domain.MeetingState2x import org.bigbluebutton.core.running.LiveMeeting trait PdfConversionInvalidErrorSysPubMsgHdlr { - this: PresentationPodHdlrs => + this: PresentationPodHdlrs => def handle( msg: PdfConversionInvalidErrorSysPubMsg, state: MeetingState2x, diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/presentationpod/PresentationConversionCompletedSysPubMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/presentationpod/PresentationConversionCompletedSysPubMsgHdlr.scala index 2cdac5898bd2..b39ba0d5a2d8 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/presentationpod/PresentationConversionCompletedSysPubMsgHdlr.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/presentationpod/PresentationConversionCompletedSysPubMsgHdlr.scala @@ -4,8 +4,6 @@ import org.bigbluebutton.common2.msgs._ import org.bigbluebutton.core.bus.MessageBus import org.bigbluebutton.core.domain.MeetingState2x import org.bigbluebutton.core.running.LiveMeeting -import org.bigbluebutton.common2.domain.{ PageVO } -import org.bigbluebutton.core.models.PresentationInPod trait PresentationConversionCompletedSysPubMsgHdlr { @@ -18,32 +16,33 @@ trait PresentationConversionCompletedSysPubMsgHdlr { val meetingId = liveMeeting.props.meetingProp.intId - val pages = new collection.mutable.HashMap[String, PageVO] - - msg.body.presentation.pages.foreach { p => - val page = PageVO(p.id, p.num, p.thumbUri, p.swfUri, p.txtUri, p.svgUri, p.current, p.xOffset, p.yOffset, - p.widthRatio, p.heightRatio) - pages += page.id -> page - } - - val downloadable = msg.body.presentation.downloadable - val presentationId = msg.body.presentation.id - val pres = new PresentationInPod(presentationId, msg.body.presentation.name, msg.body.presentation.current, - pages.toMap, downloadable) - val presVO = PresentationPodsApp.translatePresentationToPresentationVO(pres) - val podId = msg.body.podId - val newState = for { - pod <- PresentationPodsApp.getPresentationPod(state, podId) + pod <- PresentationPodsApp.getPresentationPod(state, msg.body.podId) + pres <- pod.getPresentation(msg.body.presentation.id) } yield { - PresentationSender.broadcastPresentationConversionCompletedEvtMsg(bus, meetingId, - pod.id, msg.header.userId, msg.body.messageKey, msg.body.code, presVO) - PresentationSender.broadcastSetPresentationDownloadableEvtMsg(bus, meetingId, pod.id, - msg.header.userId, presentationId, downloadable, pres.name) + val presVO = PresentationPodsApp.translatePresentationToPresentationVO(pres) + + PresentationSender.broadcastPresentationConversionCompletedEvtMsg( + bus, + meetingId, + pod.id, + msg.header.userId, + msg.body.messageKey, + msg.body.code, + presVO + ) + PresentationSender.broadcastSetPresentationDownloadableEvtMsg( + bus, + meetingId, + pod.id, + msg.header.userId, + pres.id, + pres.downloadable, + pres.name + ) var pods = state.presentationPodManager.addPod(pod) pods = pods.addPresentationToPod(pod.id, pres) - pods = pods.setCurrentPresentation(pod.id, pres.id) state.update(pods) } diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/presentationpod/PresentationConversionEndedSysMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/presentationpod/PresentationConversionEndedSysMsgHdlr.scala new file mode 100755 index 000000000000..ffedc00c38cf --- /dev/null +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/presentationpod/PresentationConversionEndedSysMsgHdlr.scala @@ -0,0 +1,40 @@ +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 PresentationConversionEndedSysMsgHdlr { + this: PresentationPodHdlrs => + + def handle(msg: PresentationConversionEndedSysMsg, state: MeetingState2x, + liveMeeting: LiveMeeting, bus: MessageBus): MeetingState2x = { + + def broadcastEvent(msg: PresentationConversionEndedSysMsg): Unit = { + val routing = Routing.addMsgToClientRouting( + MessageTypes.BROADCAST_TO_MEETING, + liveMeeting.props.meetingProp.intId, msg.header.userId + ) + val envelope = BbbCoreEnvelope(PresentationConversionEndedEventMsg.NAME, routing) + val header = BbbClientMsgHeader( + PresentationConversionEndedEventMsg.NAME, + liveMeeting.props.meetingProp.intId, msg.header.userId + ) + + val body = PresentationConversionEndedEventMsgBody( + podId = msg.body.podId, + presentationId = msg.body.presentationId, + presName = msg.body.presName + ) + val event = PresentationConversionEndedEventMsg(header, body) + val msgEvent = BbbCommonEnvCoreMsg(envelope, event) + bus.outGW.send(msgEvent) + } + + broadcastEvent(msg) + + state + } + +} diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/presentationpod/PresentationConversionUpdatePubMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/presentationpod/PresentationConversionUpdatePubMsgHdlr.scala index a50462b3263c..89bca3a68db1 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/presentationpod/PresentationConversionUpdatePubMsgHdlr.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/presentationpod/PresentationConversionUpdatePubMsgHdlr.scala @@ -22,8 +22,13 @@ trait PresentationConversionUpdatePubMsgHdlr { liveMeeting.props.meetingProp.intId, msg.header.userId ) - val body = PresentationConversionUpdateEvtMsgBody(msg.body.podId, msg.body.messageKey, - msg.body.code, msg.body.presentationId, msg.body.presName) + val body = PresentationConversionUpdateEvtMsgBody( + msg.body.podId, + msg.body.messageKey, + msg.body.code, + msg.body.presentationId, + msg.body.presName + ) val event = PresentationConversionUpdateEvtMsg(header, body) val msgEvent = BbbCommonEnvCoreMsg(envelope, event) bus.outGW.send(msgEvent) diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/presentationpod/PresentationPageConversionStartedSysMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/presentationpod/PresentationPageConversionStartedSysMsgHdlr.scala new file mode 100755 index 000000000000..1294a035111f --- /dev/null +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/presentationpod/PresentationPageConversionStartedSysMsgHdlr.scala @@ -0,0 +1,66 @@ +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.models.PresentationInPod +import org.bigbluebutton.core.running.LiveMeeting + +trait PresentationPageConversionStartedSysMsgHdlr { + this: PresentationPodHdlrs => + + def handle(msg: PresentationPageConversionStartedSysMsg, state: MeetingState2x, + liveMeeting: LiveMeeting, bus: MessageBus): MeetingState2x = { + + def broadcastEvent(msg: PresentationPageConversionStartedSysMsg): Unit = { + val routing = Routing.addMsgToClientRouting( + MessageTypes.BROADCAST_TO_MEETING, + liveMeeting.props.meetingProp.intId, msg.header.userId + ) + val envelope = BbbCoreEnvelope(PresentationPageConversionStartedSysMsg.NAME, routing) + val header = BbbClientMsgHeader( + PresentationPageConversionStartedSysMsg.NAME, + liveMeeting.props.meetingProp.intId, msg.header.userId + ) + + val body = PresentationPageConversionStartedSysMsgBody( + podId = msg.body.podId, + presentationId = msg.body.presentationId, + current = msg.body.current, + presName = msg.body.presName, + downloadable = msg.body.downloadable, + authzToken = msg.body.authzToken, + numPages = msg.body.numPages + ) + val event = PresentationPageConversionStartedSysMsg(header, body) + val msgEvent = BbbCommonEnvCoreMsg(envelope, event) + bus.outGW.send(msgEvent) + } + + val downloadable = msg.body.downloadable + val presentationId = msg.body.presentationId + val podId = msg.body.podId + + val pres = new PresentationInPod(presentationId, msg.body.presName, msg.body.current, Map.empty, downloadable) + + val newState = for { + pod <- PresentationPodsApp.getPresentationPod(state, podId) + } yield { + var pods = state.presentationPodManager.addPod(pod) + pods = pods.addPresentationToPod(pod.id, pres) + if (msg.body.current) { + pods = pods.setCurrentPresentation(pod.id, pres.id) + } + + state.update(pods) + } + + broadcastEvent(msg) + + newState match { + case Some(ns) => ns + case None => state + } + + } +} diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/presentationpod/PresentationPageConvertedSysMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/presentationpod/PresentationPageConvertedSysMsgHdlr.scala new file mode 100755 index 000000000000..ccffb183ee4a --- /dev/null +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/presentationpod/PresentationPageConvertedSysMsgHdlr.scala @@ -0,0 +1,78 @@ +package org.bigbluebutton.core.apps.presentationpod + +import org.bigbluebutton.common2.domain.PresentationPageVO +import org.bigbluebutton.common2.msgs._ +import org.bigbluebutton.core.bus.MessageBus +import org.bigbluebutton.core.domain.MeetingState2x +import org.bigbluebutton.core.models.{ PresentationInPod, PresentationPage } +import org.bigbluebutton.core.running.LiveMeeting + +trait PresentationPageConvertedSysMsgHdlr { + this: PresentationPodHdlrs => + + def handle( + msg: PresentationPageConvertedSysMsg, + state: MeetingState2x, + liveMeeting: LiveMeeting, + bus: MessageBus + ): MeetingState2x = { + + def broadcastEvent(msg: PresentationPageConvertedSysMsg): Unit = { + val routing = Routing.addMsgToClientRouting( + MessageTypes.BROADCAST_TO_MEETING, + liveMeeting.props.meetingProp.intId, msg.header.userId + ) + val envelope = BbbCoreEnvelope(PresentationPageConvertedEventMsg.NAME, routing) + val header = BbbClientMsgHeader( + PresentationPageConvertedEventMsg.NAME, + liveMeeting.props.meetingProp.intId, msg.header.userId + ) + + val page = PresentationPageVO( + id = msg.body.page.id, + num = msg.body.page.num, + urls = msg.body.page.urls, + current = msg.body.page.current + ) + + val body = PresentationPageConvertedEventMsgBody( + msg.body.podId, + msg.body.messageKey, + msg.body.code, + msg.body.presentationId, + msg.body.numberOfPages, + msg.body.pagesCompleted, + msg.body.presName, + page + ) + val event = PresentationPageConvertedEventMsg(header, body) + val msgEvent = BbbCommonEnvCoreMsg(envelope, event) + bus.outGW.send(msgEvent) + } + + val page = PresentationPage( + msg.body.page.id, + msg.body.page.num, + msg.body.page.urls, + msg.body.page.current + ) + + val newState = for { + pod <- PresentationPodsApp.getPresentationPod(state, msg.body.podId) + pres <- pod.getPresentation(msg.body.presentationId) + } yield { + val newPres = PresentationInPod.addPage(pres, page) + var pods = state.presentationPodManager.addPod(pod) + pods = pods.addPresentationToPod(pod.id, newPres) + + state.update(pods) + } + + broadcastEvent(msg) + + newState match { + case Some(ns) => ns + case None => state + } + } +} diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/presentationpod/PresentationPodHdlrs.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/presentationpod/PresentationPodHdlrs.scala index bcb6bb2895c6..965d43e9ea1a 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/presentationpod/PresentationPodHdlrs.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/presentationpod/PresentationPodHdlrs.scala @@ -20,7 +20,10 @@ class PresentationPodHdlrs(implicit val context: ActorContext) with PresentationUploadTokenReqMsgHdlr with ResizeAndMovePagePubMsgHdlr with SyncGetPresentationPodsMsgHdlr - with RemovePresentationPodPubMsgHdlr { + with RemovePresentationPodPubMsgHdlr + with PresentationPageConvertedSysMsgHdlr + with PresentationPageConversionStartedSysMsgHdlr + with PresentationConversionEndedSysMsgHdlr { val log = Logging(context.system, getClass) } diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/presentationpod/PresentationPodsApp.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/presentationpod/PresentationPodsApp.scala index 2440540dbf2f..e7210ab82a4f 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/presentationpod/PresentationPodsApp.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/presentationpod/PresentationPodsApp.scala @@ -41,10 +41,28 @@ object PresentationPodsApp { def translatePresentationPodToVO(pod: PresentationPod): PresentationPodVO = { val presentationObjects = pod.presentations - val presentationVOs = presentationObjects.values.map(p => PresentationVO(p.id, p.name, p.current, - p.pages.values.toVector, p.downloadable)).toVector + val presentationVOs = presentationObjects.values.map { p => + val pages = p.pages.values.map { page => + PageVO( + 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, + xOffset = page.xOffset, + yOffset = page.yOffset, + widthRatio = page.widthRatio, + heightRatio = page.heightRatio + ) + } + + PresentationVO(p.id, p.name, p.current, + pages.toVector, p.downloadable) + } - PresentationPodVO(pod.id, pod.currentPresenter, presentationVOs) + PresentationPodVO(pod.id, pod.currentPresenter, presentationVOs.toVector) } def findPodsWhereUserIsPresenter(mgr: PresentationPodManager, userId: String): Vector[PresentationPod] = { @@ -57,7 +75,22 @@ object PresentationPodsApp { } def translatePresentationToPresentationVO(pres: PresentationInPod): PresentationVO = { - PresentationVO(pres.id, pres.name, pres.current, pres.pages.values.toVector, pres.downloadable) + val pages = pres.pages.values.map { page => + PageVO( + 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, + xOffset = page.xOffset, + yOffset = page.yOffset, + widthRatio = page.widthRatio, + heightRatio = page.heightRatio + ) + } + PresentationVO(pres.id, pres.name, pres.current, pages.toVector, pres.downloadable) } def setCurrentPresentationInPod(state: MeetingState2x, podId: String, nextCurrentPresId: String): Option[PresentationPod] = { diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/presentationpod/ResizeAndMovePagePubMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/presentationpod/ResizeAndMovePagePubMsgHdlr.scala index eb77c7a1b86b..138e513e0039 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/presentationpod/ResizeAndMovePagePubMsgHdlr.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/presentationpod/ResizeAndMovePagePubMsgHdlr.scala @@ -1,11 +1,11 @@ package org.bigbluebutton.core.apps.presentationpod -import org.bigbluebutton.common2.domain.PageVO import org.bigbluebutton.common2.msgs._ import org.bigbluebutton.core.bus.MessageBus import org.bigbluebutton.core.domain.MeetingState2x import org.bigbluebutton.core.running.LiveMeeting import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait } +import org.bigbluebutton.core.models.PresentationPage trait ResizeAndMovePagePubMsgHdlr extends RightsManagementTrait { this: PresentationPodHdlrs => @@ -13,7 +13,7 @@ trait ResizeAndMovePagePubMsgHdlr extends RightsManagementTrait { def handle(msg: ResizeAndMovePagePubMsg, state: MeetingState2x, liveMeeting: LiveMeeting, bus: MessageBus): MeetingState2x = { - def broadcastEvent(msg: ResizeAndMovePagePubMsg, podId: String, page: PageVO): Unit = { + def broadcastEvent(msg: ResizeAndMovePagePubMsg, podId: String, page: PresentationPage): Unit = { val routing = Routing.addMsgToClientRouting( MessageTypes.BROADCAST_TO_MEETING, liveMeeting.props.meetingProp.intId, msg.header.userId diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/Polls.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/Polls.scala index a231ab6c7f91..4aab4ac88c3d 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/Polls.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/Polls.scala @@ -25,7 +25,7 @@ object Polls { for { pod <- state.presentationPodManager.getDefaultPod() pres <- pod.getCurrentPresentation() - page <- pres.getCurrentPage(pres) + page <- PresentationInPod.getCurrentPage(pres) pageId: String = if (pollId.contains("deskshare")) "deskshare" else page.id stampedPollId: String = pageId + "/" + System.currentTimeMillis() numRespondents: Int = Users2x.numUsers(lm.users2x) - 1 // subtract the presenter @@ -42,7 +42,7 @@ object Polls { for { pod <- state.presentationPodManager.getDefaultPod() pres <- pod.getCurrentPresentation() - page <- pres.getCurrentPage(pres) + page <- PresentationInPod.getCurrentPage(pres) curPoll <- getRunningPollThatStartsWith(page.id, lm.polls) } yield { stopPoll(curPoll.id, lm.polls) @@ -73,7 +73,7 @@ object Polls { for { pod <- state.presentationPodManager.getDefaultPod() pres <- pod.getCurrentPresentation() - page <- pres.getCurrentPage(pres) + page <- PresentationInPod.getCurrentPage(pres) } yield { val pageId = if (poll.id.contains("deskshare")) "deskshare" else page.id val updatedShape = shape + ("whiteboardId" -> pageId) @@ -98,7 +98,7 @@ object Polls { val poll = for { pod <- state.presentationPodManager.getDefaultPod() pres <- pod.getCurrentPresentation() - page <- pres.getCurrentPage(pres) + page <- PresentationInPod.getCurrentPage(pres) curPoll <- getRunningPollThatStartsWith(page.id, lm.polls) } yield curPoll @@ -143,7 +143,7 @@ object Polls { for { pod <- state.presentationPodManager.getDefaultPod() pres <- pod.getCurrentPresentation() - page <- pres.getCurrentPage(pres) + page <- PresentationInPod.getCurrentPage(pres) pageId: String = if (pollId.contains("deskshare")) "deskshare" else page.id stampedPollId: String = pageId + "/" + System.currentTimeMillis() numRespondents: Int = Users2x.numUsers(lm.users2x) - 1 // subtract the presenter diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/PresentationPods.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/PresentationPods.scala index 40ba0b8e779b..9cf85357998d 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/PresentationPods.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/PresentationPods.scala @@ -1,6 +1,7 @@ package org.bigbluebutton.core.models import org.bigbluebutton.common2.domain.PageVO +import org.bigbluebutton.core.models.PresentationInPod import org.bigbluebutton.core.util.RandomStringGenerator object PresentationPodFactory { @@ -18,8 +19,22 @@ object PresentationPodFactory { } } -case class PresentationInPod(id: String, name: String, current: Boolean = false, - pages: scala.collection.immutable.Map[String, PageVO], downloadable: Boolean) { +case class PresentationPage( + id: String, + num: Int, + urls: Map[String, String], + current: Boolean = false, + xOffset: Double = 0, + yOffset: Double = 0, + widthRatio: Double = 100D, + heightRatio: Double = 100D +) + +object PresentationInPod { + def addPage(pres: PresentationInPod, page: PresentationPage): PresentationInPod = { + val newPages = pres.pages + (page.id -> page) + pres.copy(pages = newPages) + } def makePageCurrent(pres: PresentationInPod, pageId: String): Option[PresentationInPod] = { pres.pages.get(pageId) match { @@ -33,12 +48,20 @@ case class PresentationInPod(id: String, name: String, current: Boolean = false, } } - def getCurrentPage(pres: PresentationInPod): Option[PageVO] = { + def getCurrentPage(pres: PresentationInPod): Option[PresentationPage] = { pres.pages.values find (p => p.current) } } +case class PresentationInPod( + id: String, + name: String, + current: Boolean = false, + pages: scala.collection.immutable.Map[String, PresentationPage], + downloadable: Boolean +) + object PresentationPod { val DEFAULT_PRESENTATION_POD = "DEFAULT_PRESENTATION_POD" } @@ -99,7 +122,7 @@ case class PresentationPod(id: String, currentPresenter: String, def setCurrentPage(presentationId: String, pageId: String): Option[PresentationPod] = { for { pres <- presentations.get(presentationId) - newPres <- pres.makePageCurrent(pres, pageId) + newPres <- PresentationInPod.makePageCurrent(pres, pageId) } yield { addPresentation(deactivateCurrentPage(newPres, pageId)) } @@ -129,7 +152,7 @@ case class PresentationPod(id: String, currentPresenter: String, def resizePage(presentationId: String, pageId: String, xOffset: Double, yOffset: Double, widthRatio: Double, - heightRatio: Double): Option[(PresentationPod, PageVO)] = { + heightRatio: Double): Option[(PresentationPod, PresentationPage)] = { // Force coordinate that are out-of-bounds inside valid values // 0.25D is 400% zoom // 100D-checkedWidth is the maximum the page can be moved over diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/pubsub/senders/ReceivedJsonMsgHandlerActor.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/pubsub/senders/ReceivedJsonMsgHandlerActor.scala index 2db88ad68a86..91dbac6f96e6 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/pubsub/senders/ReceivedJsonMsgHandlerActor.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/pubsub/senders/ReceivedJsonMsgHandlerActor.scala @@ -219,6 +219,14 @@ class ReceivedJsonMsgHandlerActor( routeGenericMsg[PresentationPageCountErrorSysPubMsg](envelope, jsonNode) case PresentationPageGeneratedSysPubMsg.NAME => routeGenericMsg[PresentationPageGeneratedSysPubMsg](envelope, jsonNode) + case PresentationPageConvertedSysMsg.NAME => + routeGenericMsg[PresentationPageConvertedSysMsg](envelope, jsonNode) + case PresentationPageConversionStartedSysMsg.NAME => + routeGenericMsg[PresentationPageConversionStartedSysMsg](envelope, jsonNode) + case PresentationConversionEndedSysMsg.NAME => + routeGenericMsg[PresentationConversionEndedSysMsg](envelope, jsonNode) + case PresentationConversionRequestReceivedSysMsg.NAME => + routeGenericMsg[PresentationConversionRequestReceivedSysMsg](envelope, jsonNode) case PresentationConversionCompletedSysPubMsg.NAME => routeGenericMsg[PresentationConversionCompletedSysPubMsg](envelope, jsonNode) case PdfConversionInvalidErrorSysPubMsg.NAME => diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/MeetingActor.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/MeetingActor.scala index 5ea402cddedb..e14e30a7e0c4 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/MeetingActor.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/MeetingActor.scala @@ -438,6 +438,9 @@ class MeetingActor( case m: PresentationPageCountErrorSysPubMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus) case m: PresentationUploadTokenReqMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus) case m: ResizeAndMovePagePubMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus) + case m: PresentationPageConvertedSysMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus) + case m: PresentationPageConversionStartedSysMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus) + case m: PresentationConversionEndedSysMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus) // Caption case m: EditCaptionHistoryPubMsg => captionApp2x.handle(m, liveMeeting, msgBus) diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/AnalyticsActor.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/AnalyticsActor.scala index f65e7bf7276f..d299cf706bb3 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/AnalyticsActor.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/AnalyticsActor.scala @@ -100,12 +100,26 @@ class AnalyticsActor extends Actor with ActorLogging { case m: BreakoutRoomEndedEvtMsg => logMessage(msg) // Presentation - case m: PresentationConversionCompletedSysPubMsg => logMessage(msg) + //case m: PresentationConversionCompletedSysPubMsg => logMessage(msg) case m: PdfConversionInvalidErrorSysPubMsg => logMessage(msg) case m: SetCurrentPresentationPubMsg => logMessage(msg) case m: SetCurrentPresentationEvtMsg => logMessage(msg) case m: SetPresentationDownloadablePubMsg => logMessage(msg) case m: SetPresentationDownloadableEvtMsg => logMessage(msg) + //case m: PresentationPageConvertedSysMsg => logMessage(msg) + //case m: PresentationPageConvertedEventMsg => logMessage(msg) + case m: PresentationPageConversionStartedSysMsg => logMessage(msg) + case m: PresentationConversionEndedSysMsg => logMessage(msg) + case m: PresentationConversionRequestReceivedSysMsg => logMessage(msg) + //case m: PresentationConversionCompletedEvtMsg => logMessage(msg) + case m: GetAllPresentationPodsReqMsg => logMessage(msg) + //case m: PresentationPageGeneratedSysPubMsg => logMessage(msg) + //case m: PresentationPageGeneratedEvtMsg => logMessage(msg) + //case m: ResizeAndMovePagePubMsg => logMessage(msg) + case m: PresentationConversionUpdateSysPubMsg => logMessage(msg) + case m: PresentationConversionUpdateEvtMsgBody => logMessage(msg) + case m: PresentationPageCountErrorSysPubMsg => logMessage(msg) + case m: PresentationPageCountErrorEvtMsg => logMessage(msg) // Group Chats case m: SendGroupChatMessageMsg => logMessage(msg) diff --git a/bbb-common-message/src/main/scala/org/bigbluebutton/common2/domain/Presentation.scala b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/domain/Presentation.scala index 925c5b2c1ad5..a7d55b635928 100755 --- a/bbb-common-message/src/main/scala/org/bigbluebutton/common2/domain/Presentation.scala +++ b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/domain/Presentation.scala @@ -9,3 +9,21 @@ case class PageVO(id: String, num: Int, thumbUri: String = "", swfUri: String, case class PresentationPodVO(id: String, currentPresenter: String, presentations: Vector[PresentationVO]) + +case class PresentationPageConvertedVO( + id: String, + num: Int, + urls: Map[String, String], + current: Boolean = false +) + +case class PresentationPageVO( + id: String, + num: Int, + urls: Map[String, String], + current: Boolean = false, + xOffset: Double = 0, + yOffset: Double = 0, + widthRatio: Double = 100D, + heightRatio: Double = 100D +) diff --git a/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/PresentationPodsMsgs.scala b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/PresentationPodsMsgs.scala index 2d36a93c848a..75623c81b32e 100755 --- a/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/PresentationPodsMsgs.scala +++ b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/PresentationPodsMsgs.scala @@ -1,6 +1,6 @@ package org.bigbluebutton.common2.msgs -import org.bigbluebutton.common2.domain.{ PresentationPodVO, PresentationVO } +import org.bigbluebutton.common2.domain.{ PageVO, PresentationPageConvertedVO, PresentationPageVO, PresentationPodVO, PresentationVO } // ------------ client to akka-apps ------------ object CreateNewPresentationPodPubMsg { val NAME = "CreateNewPresentationPodPubMsg" } @@ -73,8 +73,8 @@ case class PdfConversionInvalidErrorSysPubMsg( body: PdfConversionInvalidErrorSysPubMsgBody ) extends StandardMsg case class PdfConversionInvalidErrorSysPubMsgBody(podId: String, messageKey: String, code: String, presentationId: String, - bigPageNumber: Int, bigPageSize: Int, presName: String) - + bigPageNumber: Int, bigPageSize: Int, presName: String) + object PresentationPageGeneratedSysPubMsg { val NAME = "PresentationPageGeneratedSysPubMsg" } case class PresentationPageGeneratedSysPubMsg( header: BbbClientMsgHeader, @@ -90,6 +90,63 @@ case class PresentationConversionCompletedSysPubMsg( ) extends StandardMsg case class PresentationConversionCompletedSysPubMsgBody(podId: String, messageKey: String, code: String, presentation: PresentationVO) + +object PresentationPageConvertedSysMsg { val NAME = "PresentationPageConvertedSysMsg" } +case class PresentationPageConvertedSysMsg( + header: BbbClientMsgHeader, + body: PresentationPageConvertedSysMsgBody +) extends StandardMsg +case class PresentationPageConvertedSysMsgBody( + podId: String, + messageKey: String, + code: String, + presentationId: String, + numberOfPages: Int, + pagesCompleted: Int, + presName: String, + page: PresentationPageConvertedVO +) + +object PresentationConversionRequestReceivedSysMsg { val NAME = "PresentationConversionRequestReceivedSysMsg" } +case class PresentationConversionRequestReceivedSysMsg( + header: BbbClientMsgHeader, + body: PresentationConversionRequestReceivedSysMsgBody +) extends StandardMsg +case class PresentationConversionRequestReceivedSysMsgBody( + podId: String, + presentationId: String, + current: Boolean, + presName: String, + downloadable: Boolean, + authzToken: String +) + +object PresentationPageConversionStartedSysMsg { val NAME = "PresentationPageConversionStartedSysMsg" } +case class PresentationPageConversionStartedSysMsg( + header: BbbClientMsgHeader, + body: PresentationPageConversionStartedSysMsgBody +) extends StandardMsg +case class PresentationPageConversionStartedSysMsgBody( + podId: String, + presentationId: String, + current: Boolean, + presName: String, + downloadable: Boolean, + authzToken: String, + numPages: Int +) + +object PresentationConversionEndedSysMsg { val NAME = "PresentationConversionEndedSysMsg" } +case class PresentationConversionEndedSysMsg( + header: BbbClientMsgHeader, + body: PresentationConversionEndedSysMsgBody +) extends StandardMsg +case class PresentationConversionEndedSysMsgBody( + podId: String, + presentationId: String, + presName: String +) + // ------------ bbb-common-web to akka-apps ------------ // ------------ akka-apps to client ------------ @@ -125,6 +182,62 @@ object PresentationPageGeneratedEvtMsg { val NAME = "PresentationPageGeneratedEv case class PresentationPageGeneratedEvtMsg(header: BbbClientMsgHeader, body: PresentationPageGeneratedEvtMsgBody) extends BbbCoreMsg case class PresentationPageGeneratedEvtMsgBody(podId: String, messageKey: String, code: String, presentationId: String, numberOfPages: Int, pagesCompleted: Int, presName: String) +object PresentationPageConvertedEventMsg { val NAME = "PresentationPageConvertedEventMsg" } +case class PresentationPageConvertedEventMsg( + header: BbbClientMsgHeader, + body: PresentationPageConvertedEventMsgBody +) extends BbbCoreMsg +case class PresentationPageConvertedEventMsgBody( + podId: String, + messageKey: String, + code: String, + presentationId: String, + numberOfPages: Int, + pagesCompleted: Int, + presName: String, + page: PresentationPageVO +) + +object PresentationConversionRequestReceivedEventMsg { val NAME = "PresentationConversionRequestReceivedEventMsg" } +case class PresentationConversionRequestReceivedEventMsg( + header: BbbClientMsgHeader, + body: PresentationConversionRequestReceivedEventMsgBody +) extends StandardMsg +case class PresentationConversionRequestReceivedEventMsgBody( + podId: String, + presentationId: String, + current: Boolean, + presName: String, + downloadable: Boolean, + authzToken: String +) + +object PresentationPageConversionStartedEventMsg { val NAME = "PresentationPageConversionStartedEventMsg" } +case class PresentationPageConversionStartedEventMsg( + header: BbbClientMsgHeader, + body: PresentationPageConversionStartedEventMsgBody +) extends StandardMsg +case class PresentationPageConversionStartedEventMsgBody( + podId: String, + presentationId: String, + current: Boolean, + presName: String, + downloadable: Boolean, + numPages: Int, + authzToken: String +) + +object PresentationConversionEndedEventMsg { val NAME = "PresentationConversionEndedEventMsg" } +case class PresentationConversionEndedEventMsg( + header: BbbClientMsgHeader, + body: PresentationConversionEndedEventMsgBody +) extends StandardMsg +case class PresentationConversionEndedEventMsgBody( + podId: String, + presentationId: String, + presName: String +) + object PresentationConversionCompletedEvtMsg { val NAME = "PresentationConversionCompletedEvtMsg" } case class PresentationConversionCompletedEvtMsg(header: BbbClientMsgHeader, body: PresentationConversionCompletedEvtMsgBody) extends BbbCoreMsg case class PresentationConversionCompletedEvtMsgBody(podId: String, messageKey: String, code: String, presentation: PresentationVO) diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/ConversionMessageConstants.java b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/ConversionMessageConstants.java index e585a4000ad5..5c4618a982f4 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/ConversionMessageConstants.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/ConversionMessageConstants.java @@ -35,6 +35,7 @@ public class ConversionMessageConstants { public static final String GENERATED_TEXTFILES_KEY = "GENERATED_TEXTFILES"; public static final String GENERATING_SVGIMAGES_KEY = "GENERATING_SVGIMAGES"; 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"; private ConversionMessageConstants() { diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/DocumentConversionServiceImp.java b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/DocumentConversionServiceImp.java index 3f979ef8b725..3a1c4f70fbef 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/DocumentConversionServiceImp.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/DocumentConversionServiceImp.java @@ -23,38 +23,30 @@ import java.util.Map; import org.bigbluebutton.api2.IBbbWebApiGWApp; -import org.bigbluebutton.presentation.imp.ImageToSwfSlidesGenerationService; -import org.bigbluebutton.presentation.imp.OfficeToPdfConversionService; -import org.bigbluebutton.presentation.imp.PdfToSwfSlidesGenerationService; +import org.bigbluebutton.presentation.imp.*; +import org.bigbluebutton.presentation.messages.DocPageConversionStarted; +import org.bigbluebutton.presentation.messages.DocConversionRequestReceived; +import org.bigbluebutton.presentation.messages.DocPageCountExceeded; +import org.bigbluebutton.presentation.messages.DocPageCountFailed; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.gson.Gson; public class DocumentConversionServiceImp implements DocumentConversionService { - private static Logger log = LoggerFactory - .getLogger(DocumentConversionServiceImp.class); + private static Logger log = LoggerFactory.getLogger(DocumentConversionServiceImp.class); private IBbbWebApiGWApp gw; private OfficeToPdfConversionService officeToPdfConversionService; - private PdfToSwfSlidesGenerationService pdfToSwfSlidesGenerationService; - private ImageToSwfSlidesGenerationService imageToSwfSlidesGenerationService; + private SwfSlidesGenerationProgressNotifier notifier; + + private PresentationFileProcessor presentationFileProcessor; public void processDocument(UploadedPresentation pres) { - SupportedDocumentFilter sdf = new SupportedDocumentFilter(gw); - Map logData = new HashMap(); - logData.put("podId", pres.getPodId()); - logData.put("meetingId", pres.getMeetingId()); - logData.put("presId", pres.getId()); - logData.put("filename", pres.getName()); - logData.put("current", pres.isCurrent()); - logData.put("logCode", "presentation_conversion_start"); - logData.put("message", "Start presentation conversion."); + SupportedDocumentFilter sdf = new SupportedDocumentFilter(gw); - Gson gson = new Gson(); - String logStr = gson.toJson(logData); - log.info(" --analytics-- data={}", logStr); + sendDocConversionRequestReceived(pres); if (sdf.isSupported(pres)) { String fileType = pres.getFileType(); @@ -75,10 +67,11 @@ public void processDocument(UploadedPresentation pres) { ocsf.sendProgress(pres); } } else if (SupportedFileTypes.isPdfFile(fileType)) { - pdfToSwfSlidesGenerationService.generateSlides(pres); + presentationFileProcessor.process(pres); } else if (SupportedFileTypes.isImageFile(fileType)) { - imageToSwfSlidesGenerationService.generateSlides(pres); + presentationFileProcessor.process(pres); } else { + Map logData = new HashMap(); logData = new HashMap(); logData.put("podId", pres.getPodId()); logData.put("meetingId", pres.getMeetingId()); @@ -87,12 +80,14 @@ public void processDocument(UploadedPresentation pres) { logData.put("current", pres.isCurrent()); logData.put("logCode", "supported_file_not_handled"); logData.put("message", "Supported file not handled."); - gson = new Gson(); - logStr = gson.toJson(logData); + + Gson gson = new Gson(); + String logStr = gson.toJson(logData); log.warn(" --analytics-- data={}", logStr); } } else { + Map logData = new HashMap(); logData = new HashMap(); logData.put("podId", pres.getPodId()); logData.put("meetingId", pres.getMeetingId()); @@ -101,22 +96,58 @@ public void processDocument(UploadedPresentation pres) { logData.put("current", pres.isCurrent()); logData.put("logCode", "unsupported_file_format"); logData.put("message", "Unsupported file format"); - gson = new Gson(); - logStr = gson.toJson(logData); + + Gson gson = new Gson(); + String logStr = gson.toJson(logData); log.error(" --analytics-- data={}", logStr); + + logData.clear(); + + logData.put("podId", pres.getPodId()); + logData.put("meetingId", pres.getMeetingId()); + logData.put("presId", pres.getId()); + logData.put("filename", pres.getName()); + logData.put("current", pres.isCurrent()); + logData.put("logCode", "presentation_conversion_end"); + logData.put("message", "End presentation conversion."); + + logStr = gson.toJson(logData); + log.info(" --analytics-- data={}", logStr); + + notifier.sendConversionCompletedMessage(pres); } - logData = new HashMap(); - logData.put("podId", pres.getPodId()); - logData.put("meetingId", pres.getMeetingId()); - logData.put("presId", pres.getId()); - logData.put("filename", pres.getName()); - logData.put("current", pres.isCurrent()); - logData.put("logCode", "presentation_conversion_end"); - logData.put("message", "End presentation conversion."); - gson = new Gson(); - logStr = gson.toJson(logData); - log.info(" --analytics-- data={}", logStr); + } + + private void sendDocConversionRequestReceived(UploadedPresentation pres) { + if (! pres.isConversionStarted()) { + Map logData = new HashMap(); + + logData.put("podId", pres.getPodId()); + logData.put("meetingId", pres.getMeetingId()); + logData.put("presId", pres.getId()); + logData.put("filename", pres.getName()); + logData.put("current", pres.isCurrent()); + logData.put("authzToken", pres.getAuthzToken()); + logData.put("logCode", "presentation_conversion_start"); + logData.put("message", "Start presentation conversion."); + + Gson gson = new Gson(); + String logStr = gson.toJson(logData); + log.info(" --analytics-- data={}", logStr); + + pres.startConversion(); + + DocConversionRequestReceived progress = new DocConversionRequestReceived( + pres.getPodId(), + pres.getMeetingId(), + pres.getId(), + pres.getName(), + pres.getAuthzToken(), + pres.isDownloadable(), + pres.isCurrent()); + notifier.sendDocConversionProgress(progress); + } } public void setBbbWebApiGWApp(IBbbWebApiGWApp m) { @@ -127,13 +158,11 @@ public void setOfficeToPdfConversionService(OfficeToPdfConversionService s) { officeToPdfConversionService = s; } - public void setPdfToSwfSlidesGenerationService( - PdfToSwfSlidesGenerationService s) { - pdfToSwfSlidesGenerationService = s; + public void setSwfSlidesGenerationProgressNotifier(SwfSlidesGenerationProgressNotifier notifier) { + this.notifier = notifier; } - public void setImageToSwfSlidesGenerationService( - ImageToSwfSlidesGenerationService s) { - imageToSwfSlidesGenerationService = s; + public void setPresentationFileProcessor(PresentationFileProcessor presentationFileProcessor) { + this.presentationFileProcessor = presentationFileProcessor; } } diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/PdfToSwfSlide.java b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/PdfToSwfSlide.java index 76f5da9a6ca4..f39d50077e9f 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/PdfToSwfSlide.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/PdfToSwfSlide.java @@ -41,22 +41,21 @@ public class PdfToSwfSlide { private volatile boolean done = false; private File slide; + private File pageFile; - public PdfToSwfSlide(UploadedPresentation pres, int page) { + public PdfToSwfSlide(UploadedPresentation pres, int page, File pageFile) { this.pres = pres; this.page = page; + this.pageFile = pageFile; } public PdfToSwfSlide createSlide() { - File presentationFile = pres.getUploadedFile(); - slide = new File(presentationFile.getParent() + File.separatorChar - + "slide-" + page + ".swf"); - pdfToSwfConverter.convert(presentationFile, slide, page, pres); + 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()); + log.warn("Failed to create slide. Creating blank slide for " + slide.getAbsolutePath()); generateBlankSlide(); } diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/PngCreator.java b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/PngCreator.java index 1ba895d140d5..5bf36b529766 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/PngCreator.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/PngCreator.java @@ -18,6 +18,8 @@ package org.bigbluebutton.presentation; +import java.io.File; + public interface PngCreator { - public boolean createPng(UploadedPresentation pres); + public boolean createPng(UploadedPresentation pres, int page, File pageFile); } diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/PresentationUrlDownloadService.java b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/PresentationUrlDownloadService.java index de3e16c3a7eb..a4b217756f51 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/PresentationUrlDownloadService.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/PresentationUrlDownloadService.java @@ -59,10 +59,10 @@ public void run() { } public void processUploadedFile(String podId, String meetingId, String presId, - String filename, File presFile, Boolean current) { + String filename, File presFile, Boolean current, String authzToken) { // TODO add podId UploadedPresentation uploadedPres = new UploadedPresentation(podId, meetingId, - presId, filename, presentationBaseURL, current); + presId, filename, presentationBaseURL, current, authzToken); uploadedPres.setUploadedFile(presFile); processUploadedPresentation(uploadedPres); } @@ -145,7 +145,7 @@ public boolean accept(File dir, String name) { // Hardcode pre-uploaded presentation for breakout room to the default presentation window processUploadedFile("DEFAULT_PRESENTATION_POD", destinationMeetingId, presId, "default-" + presentationSlide.toString() + "." + filenameExt, - newPresentation, true); + newPresentation, true, "breakout-authz-token"); } public String generatePresentationId(String name) { diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/SvgImageCreator.java b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/SvgImageCreator.java index d0af7ad08d7c..f43e48ff1b02 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/SvgImageCreator.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/SvgImageCreator.java @@ -23,5 +23,5 @@ package org.bigbluebutton.presentation; public interface SvgImageCreator { - public boolean createSvgImages(UploadedPresentation pres); + public boolean createSvgImage(UploadedPresentation pres, int page); } diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/TextFileCreator.java b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/TextFileCreator.java index a412755eb32f..145bb8424a43 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/TextFileCreator.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/TextFileCreator.java @@ -20,5 +20,5 @@ package org.bigbluebutton.presentation; public interface TextFileCreator { - public boolean createTextFiles(UploadedPresentation pres); + public boolean createTextFile(UploadedPresentation pres, int page); } diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/ThumbnailCreator.java b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/ThumbnailCreator.java index 711c5b763d9b..5c82a20cb7dd 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/ThumbnailCreator.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/ThumbnailCreator.java @@ -19,6 +19,8 @@ package org.bigbluebutton.presentation; +import java.io.File; + public interface ThumbnailCreator { - public boolean createThumbnails(UploadedPresentation pres); + public boolean createThumbnail(UploadedPresentation pres, int page, File pageFile); } diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/UploadedPresentation.java b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/UploadedPresentation.java index 867982abe822..0bbb49eec26f 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/UploadedPresentation.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/UploadedPresentation.java @@ -33,8 +33,16 @@ public final class UploadedPresentation { private final String baseUrl; private boolean isDownloadable = false; private boolean current = false; - - public UploadedPresentation(String podId, String meetingId, String id, String name, String baseUrl, Boolean current) { + private String authzToken; + private boolean conversionStarted = false; + + public UploadedPresentation(String podId, + String meetingId, + String id, + String name, + String baseUrl, + Boolean current, + String authzToken) { this.podId = podId; this.meetingId = meetingId; this.id = id; @@ -42,6 +50,7 @@ public UploadedPresentation(String podId, String meetingId, String id, String na this.baseUrl = baseUrl; this.isDownloadable = false; this.current = current; + this.authzToken = authzToken; } public File getUploadedFile() { @@ -111,4 +120,16 @@ public boolean isCurrent() { public void setCurrent(Boolean value) { this.current = value; } + + public String getAuthzToken() { + return authzToken; + } + + public void startConversion() { + conversionStarted = true; + } + + public boolean isConversionStarted() { + return conversionStarted; + } } diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/ImageToSwfSlidesGenerationService.java b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/ImageToSwfSlidesGenerationService.java index 85c80db1f289..febc1fa9640a 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/ImageToSwfSlidesGenerationService.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/ImageToSwfSlidesGenerationService.java @@ -67,29 +67,35 @@ public ImageToSwfSlidesGenerationService() { executor = Executors.newFixedThreadPool(numThreads); completionService = new ExecutorCompletionService(executor); } - + public void generateSlides(UploadedPresentation pres) { - pres.setNumberOfPages(1); // There should be only one image to convert. - if (swfSlidesRequired) { - if (pres.getNumberOfPages() > 0) { - PageConverter pageConverter = determinePageConverter(pres); - convertImageToSwf(pres, pageConverter); - } - } - - /* adding accessibility */ - createTextFiles(pres); - createThumbnails(pres); - - if (svgImagesRequired) { - createSvgImages(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); } - - if (generatePngs) { - createPngImages(pres); - } - + + System.out.println("****** Conversion complete for " + pres.getName()); notifier.sendConversionCompletedMessage(pres); + } private PageConverter determinePageConverter(UploadedPresentation pres) { @@ -101,26 +107,26 @@ private PageConverter determinePageConverter(UploadedPresentation pres) { return pngToSwfConverter; } - private void createTextFiles(UploadedPresentation pres) { + private void createTextFiles(UploadedPresentation pres, int page) { log.debug("Creating textfiles for accessibility."); notifier.sendCreatingTextFilesUpdateMessage(pres); - textFileCreator.createTextFiles(pres); + textFileCreator.createTextFile(pres, page); } - private void createThumbnails(UploadedPresentation pres) { + private void createThumbnails(UploadedPresentation pres, int page) { log.debug("Creating thumbnails."); notifier.sendCreatingThumbnailsUpdateMessage(pres); - thumbnailCreator.createThumbnails(pres); + thumbnailCreator.createThumbnail(pres, page, pres.getUploadedFile()); } - private void createSvgImages(UploadedPresentation pres) { + private void createSvgImages(UploadedPresentation pres, int page) { log.debug("Creating SVG images."); notifier.sendCreatingSvgImagesUpdateMessage(pres); - svgImageCreator.createSvgImages(pres); + svgImageCreator.createSvgImage(pres, page); } - private void createPngImages(UploadedPresentation pres) { - pngCreator.createPng(pres); + private void createPngImages(UploadedPresentation pres, int page) { + pngCreator.createPng(pres, page, pres.getUploadedFile()); } private void convertImageToSwf(UploadedPresentation pres, PageConverter pageConverter) { @@ -144,8 +150,7 @@ private void resizeImage(UploadedPresentation pres, String ratio) { private void handleSlideGenerationResult(UploadedPresentation pres, ImageToSwfSlide[] slides) { long endTime = System.currentTimeMillis() + MAX_CONVERSION_TIME; - int slideGenerated = 0; - + for (int t = 0; t < slides.length; t++) { Future future = null; ImageToSwfSlide slide = null; @@ -166,8 +171,6 @@ private void handleSlideGenerationResult(UploadedPresentation pres, ImageToSwfSl slide.generateBlankSlide(); } } - slideGenerated++; - notifier.sendConversionUpdateMessage(slideGenerated, pres); } } diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/Jpeg2SwfPageConverter.java b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/Jpeg2SwfPageConverter.java index d01e52c25b3f..49f15275ac0f 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/Jpeg2SwfPageConverter.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/Jpeg2SwfPageConverter.java @@ -39,7 +39,7 @@ public boolean convert(File presentationFile, File output, int page, UploadedPre String COMMAND = SWFTOOLS_DIR + File.separatorChar + "jpeg2swf -o " + output.getAbsolutePath() + " " + presentationFile.getAbsolutePath(); - boolean done = new ExternalProcessExecutor().exec(COMMAND, 60000); + boolean done = new ExternalProcessExecutor().exec(COMMAND, 10000); if (done && output.exists()) { return true; diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/OfficeDocumentValidator2.java b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/OfficeDocumentValidator2.java index bb32ba6baf3a..0f274ec67523 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/OfficeDocumentValidator2.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/OfficeDocumentValidator2.java @@ -24,7 +24,7 @@ public boolean isValid(UploadedPresentation pres) { log.info("Running pres check " + COMMAND); - boolean done = new ExternalProcessExecutor().exec(COMMAND, 60000); + boolean done = new ExternalProcessExecutor().exec(COMMAND, 25000); if (done) { return true; diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/OfficeToPdfConversionService.java b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/OfficeToPdfConversionService.java index d182dcb2ff33..e60ec5794d86 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/OfficeToPdfConversionService.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/OfficeToPdfConversionService.java @@ -41,6 +41,7 @@ public class OfficeToPdfConversionService { private OfficeDocumentValidator2 officeDocumentValidator; private final OfficeManager officeManager; private final OfficeDocumentConverter documentConverter; + private boolean skipOfficePrecheck = false; public OfficeToPdfConversionService() { final DefaultOfficeManagerBuilder configuration = new DefaultOfficeManagerBuilder(); @@ -57,8 +58,8 @@ public OfficeToPdfConversionService() { public UploadedPresentation convertOfficeToPdf(UploadedPresentation pres) { initialize(pres); if (SupportedFileTypes.isOfficeFile(pres.getFileType())) { - boolean valid = officeDocumentValidator.isValid(pres); - if (!valid) { + // Check if we need to precheck office document + if (!skipOfficePrecheck && officeDocumentValidator.isValid(pres)) { Map logData = new HashMap<>(); logData.put("meetingId", pres.getMeetingId()); logData.put("presId", pres.getId()); @@ -128,6 +129,10 @@ public void setOfficeDocumentValidator(OfficeDocumentValidator2 v) { officeDocumentValidator = v; } + public void setSkipOfficePrecheck(boolean skipOfficePrecheck) { + this.skipOfficePrecheck = skipOfficePrecheck; + } + public void start() { try { officeManager.start(); diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/PageExtractorImp.java b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/PageExtractorImp.java index 42a6bb9cdba3..334d072e65e5 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/PageExtractorImp.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/PageExtractorImp.java @@ -29,10 +29,11 @@ public class PageExtractorImp implements PageExtractor { private static Logger log = LoggerFactory.getLogger(PageExtractorImp.class); private static final String SPACE = " "; + private static final long extractTimeout = 10000; // 10sec public boolean extractPage(File presentationFile, File output, int page) { String COMMAND = "pdfseparate -f " + page + " -l " + page + SPACE + presentationFile.getAbsolutePath() + SPACE + output.getAbsolutePath(); - return new ExternalProcessExecutor().exec(COMMAND, 60000); + return new ExternalProcessExecutor().exec(COMMAND, extractTimeout); } } diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/PageToConvert.java b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/PageToConvert.java new file mode 100755 index 000000000000..dd6eb1ef2ab7 --- /dev/null +++ b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/PageToConvert.java @@ -0,0 +1,135 @@ +package org.bigbluebutton.presentation.imp; + + +import org.bigbluebutton.presentation.*; +import java.io.File; + +public class PageToConvert { + + private UploadedPresentation pres; + private int page; + + private boolean swfSlidesRequired; + private boolean svgImagesRequired; + 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 File pageFile; + + 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) { + 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() { + return pageFile; + } + + public int getPageNumber() { + return page; + } + + public String getPresId() { + return pres.getId(); + } + + public PageToConvert convert() { + + // Only create SWF files if the configuration requires it + if (swfSlidesRequired) { + convertPdfToSwf(pres, page, pageFile); + } + + /* adding accessibility */ + createThumbnails(pres, page, pageFile); + + createTextFiles(pres, page); + + // only create SVG images if the configuration requires it + if (svgImagesRequired) { + createSvgImages(pres, page); + } + + // only create PNG images if the configuration requires it + if (generatePngs) { + createPngImages(pres, page, pageFile); + } + + return this; + } + + private void createThumbnails(UploadedPresentation pres, int page, File pageFile) { + //notifier.sendCreatingThumbnailsUpdateMessage(pres); + thumbnailCreator.createThumbnail(pres, page, pageFile); + } + + private void createTextFiles(UploadedPresentation pres, int page) { + //notifier.sendCreatingTextFilesUpdateMessage(pres); + textFileCreator.createTextFile(pres, page); + } + + private void createSvgImages(UploadedPresentation pres, int page) { + //notifier.sendCreatingSvgImagesUpdateMessage(pres); + svgImageCreator.createSvgImage(pres, page); + } + + private void createPngImages(UploadedPresentation pres, int page, File pageFile) { + 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; + } +} diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/Pdf2SwfPageConverter.java b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/Pdf2SwfPageConverter.java index dea17d7b7add..cdde5ef21d43 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/Pdf2SwfPageConverter.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/Pdf2SwfPageConverter.java @@ -51,8 +51,7 @@ public class Pdf2SwfPageConverter implements PageConverter { private String convTimeout = "7s"; private int WAIT_FOR_SEC = 7; - public boolean convert(File presentation, File output, int page, - UploadedPresentation pres) { + public boolean convert(File presentation, File output, int page, UploadedPresentation pres) { long convertStart = System.currentTimeMillis(); String source = presentation.getAbsolutePath(); @@ -64,7 +63,7 @@ public boolean convert(File presentation, File output, int page, NuProcessBuilder pb = new NuProcessBuilder(Arrays.asList("timeout", convTimeout, "/bin/sh", "-c", SWFTOOLS_DIR + File.separatorChar + "pdf2swf" + " -vv " + AVM2SWF + " -F " - + fontsDir + " -p " + Integer.toString(page) + " " + source + " -o " + + fontsDir + " " + source + " -o " + dest + " | egrep 'shape id|Updating font|Drawing' | sed 's/ / /g' | cut -d' ' -f 1-3 | sort | uniq -cw 15")); @@ -81,8 +80,7 @@ public boolean convert(File presentation, File output, int page, } long pdf2SwfEnd = System.currentTimeMillis(); - log.debug("Pdf2Swf conversion duration: {} sec", - (pdf2SwfEnd - pdf2SwfStart) / 1000); + log.debug("Pdf2Swf conversion duration: {} sec", (pdf2SwfEnd - pdf2SwfStart) / 1000); boolean timedOut = pdf2SwfEnd - pdf2SwfStart >= Integer.parseInt(convTimeout.replaceFirst("s", "")) @@ -93,6 +91,7 @@ public boolean convert(File presentation, File output, int page, + defineTextThreshold + imageTagThreshold) * 2; File destFile = new File(dest); + if (pHandler.isCommandSuccessful() && destFile.exists() && pHandler.numberOfPlacements() < placementsThreshold && pHandler.numberOfTextTags() < defineTextThreshold @@ -144,7 +143,6 @@ public boolean convert(File presentation, File output, int page, NuProcessBuilder pbPng = new NuProcessBuilder( Arrays.asList("timeout", convTimeout, "pdftocairo", "-png", "-singlefile", "-r", timedOut || twiceTotalObjects ? "72" : "150", - "-f", String.valueOf(page), "-l", String.valueOf(page), presentation.getAbsolutePath(), tempPng.getAbsolutePath() .substring(0, tempPng.getAbsolutePath().lastIndexOf('.')))); @@ -177,6 +175,7 @@ public boolean convert(File presentation, File output, int page, log.error("InterruptedException while creating SWF {}", pres.getName(), e); } + //long png2swfEnd = System.currentTimeMillis(); //log.debug("SwfTools conversion duration: {} sec", (png2swfEnd - png2swfStart) / 1000); diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/PdfPageDownscaler.java b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/PdfPageDownscaler.java new file mode 100755 index 000000000000..fa5c95b12e29 --- /dev/null +++ b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/PdfPageDownscaler.java @@ -0,0 +1,18 @@ +package org.bigbluebutton.presentation.imp; + +import java.io.File; + +public class PdfPageDownscaler { + private static final String SPACE = " "; + + public boolean downscale(File source,File dest) { + String COMMAND = "gs -sDEVICE=pdfwrite -dNOPAUSE -dQUIET -dBATCH -dFirstPage=1 -dLastPage=1 -sOutputFile=" + + dest.getAbsolutePath() + SPACE + + "/etc/bigbluebutton/nopdfmark.ps" + SPACE + + source.getAbsolutePath(); + + //System.out.println("DOWNSCALING " + COMMAND); + + return new ExternalProcessExecutor().exec(COMMAND, 10000); + } +} diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/PdfToSwfSlidesGenerationService.java b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/PdfToSwfSlidesGenerationService.java index a54c27b14cc6..4453d0d433d3 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/PdfToSwfSlidesGenerationService.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/PdfToSwfSlidesGenerationService.java @@ -19,445 +19,43 @@ package org.bigbluebutton.presentation.imp; -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; -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.*; -import org.bigbluebutton.presentation.ConversionUpdateMessage.MessageBuilder; -import org.bigbluebutton.presentation.messages.DocPageCountExceeded; -import org.bigbluebutton.presentation.messages.DocPageCountFailed; -import org.bigbluebutton.presentation.messages.PdfConversionInvalid; +import java.util.ArrayList; +import java.util.concurrent.*; +import org.bigbluebutton.presentation.messages.PageConvertProgressMessage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.gson.Gson; - public class PdfToSwfSlidesGenerationService { private static Logger log = LoggerFactory.getLogger(PdfToSwfSlidesGenerationService.class); - private SwfSlidesGenerationProgressNotifier notifier; - private PageCounterService counterService; - private PageConverter pdfToSwfConverter; private ExecutorService executor; - private ThumbnailCreator thumbnailCreator; - private PngCreator pngCreator; - private TextFileCreator textFileCreator; - private SvgImageCreator svgImageCreator; - private long bigPdfSize; - private long maxBigPdfPageSize; - private PageExtractor pageExtractor; - private long MAX_CONVERSION_TIME = 5 * 60 * 1000L * 1000L * 1000L; - private String BLANK_SLIDE; - private int MAX_SWF_FILE_SIZE; - private boolean swfSlidesRequired; - private boolean svgImagesRequired; - private boolean generatePngs; + private BlockingQueue messages = new LinkedBlockingQueue(); + + private PresentationConversionCompletionService presentationConversionCompletionService; public PdfToSwfSlidesGenerationService(int numConversionThreads) { executor = Executors.newFixedThreadPool(numConversionThreads); } - public void generateSlides(UploadedPresentation pres) { - determineNumberOfPages(pres); - if (pres.getNumberOfPages() > 0) { - if (pres.getUploadedFile().length() > bigPdfSize) { - try { - hasBigPage(pres); - } catch (BigPdfException e) { - sendFailedToConvertBigPdfMessage(e, pres); - return; - } - } - - // Only create SWF files if the configuration requires it - if (swfSlidesRequired) { - convertPdfToSwf(pres); - } - - /* adding accessibility */ - createThumbnails(pres); - createTextFiles(pres); - - // only create SVG images if the configuration requires it - if (svgImagesRequired) { - createSvgImages(pres); - } - - // only create PNG images if the configuration requires it - if (generatePngs) { - createPngImages(pres); - } - - notifier.sendConversionCompletedMessage(pres); - } - } - - private boolean determineNumberOfPages(UploadedPresentation pres) { - try { - counterService.determineNumberOfPages(pres); - return true; - } catch (CountingPageException e) { - sendFailedToCountPageMessage(e, pres); - } - return false; - } - - private boolean hasBigPage(UploadedPresentation pres) throws BigPdfException { - long lastPageSize = 0; - int currentPage = 1; - String basePresentationame = UUID.randomUUID().toString(); - if (pres.getNumberOfPages() > 1) { - while(currentPage < pres.getNumberOfPages()) { - File tempPage; - try { - tempPage = File.createTempFile(basePresentationame + "-" + currentPage, ".pdf"); - pageExtractor.extractPage(pres.getUploadedFile(), tempPage, currentPage); - lastPageSize = tempPage.length(); - // Delete the temporary file - tempPage.delete(); - } catch (IOException e) { - e.printStackTrace(); - } - - if (lastPageSize > maxBigPdfPageSize) { - throw new BigPdfException(BigPdfException.ExceptionType.PDF_HAS_BIG_PAGE, currentPage, lastPageSize); - } - - lastPageSize = 0; - currentPage++; - } - } else { - if ((int)pres.getUploadedFile().length() > bigPdfSize) { - throw new BigPdfException(BigPdfException.ExceptionType.PDF_HAS_BIG_PAGE, 1, pres.getUploadedFile().length()); + public void process(PageToConvert pageToConvert) { + Runnable task = new Runnable() { + public void run() { + pageToConvert.convert(); + PageConvertProgressMessage msg = new PageConvertProgressMessage( + pageToConvert.getPageNumber(), + pageToConvert.getPresId(), + new ArrayList<>()); + presentationConversionCompletionService.handle(msg); + pageToConvert.getPageFile().delete(); } - } + }; - - return false; + executor.execute(task); } - private void sendFailedToCountPageMessage(CountingPageException e, UploadedPresentation pres) { - MessageBuilder builder = new ConversionUpdateMessage.MessageBuilder(pres); - - if (e.getExceptionType() == CountingPageException.ExceptionType.PAGE_COUNT_EXCEPTION) { - builder.messageKey(ConversionMessageConstants.PAGE_COUNT_FAILED_KEY); - - Map logData = new HashMap<>(); - logData.put("podId", pres.getPodId()); - logData.put("meetingId", pres.getMeetingId()); - logData.put("presId", pres.getId()); - logData.put("filename", pres.getName()); - logData.put("logCode", "determine_num_pages_failed"); - logData.put("message", "Failed to determine number of pages."); - Gson gson = new Gson(); - String logStr = gson.toJson(logData); - log.error(" --analytics-- data={}", logStr, e); - - DocPageCountFailed progress = new DocPageCountFailed(pres.getPodId(), pres.getMeetingId(), - pres.getId(), pres.getId(), - pres.getName(), "notUsedYet", "notUsedYet", - pres.isDownloadable(), ConversionMessageConstants.PAGE_COUNT_FAILED_KEY); - - notifier.sendDocConversionProgress(progress); - - } else if (e.getExceptionType() == CountingPageException.ExceptionType.PAGE_EXCEEDED_EXCEPTION) { - builder.numberOfPages(e.getPageCount()); - builder.maxNumberPages(e.getMaxNumberOfPages()); - builder.messageKey(ConversionMessageConstants.PAGE_COUNT_EXCEEDED_KEY); - - Map logData = new HashMap(); - logData.put("podId", pres.getPodId()); - logData.put("meetingId", pres.getMeetingId()); - logData.put("presId", pres.getId()); - logData.put("filename", pres.getName()); - logData.put("pageCount", e.getPageCount()); - logData.put("maxNumPages", e.getMaxNumberOfPages()); - logData.put("logCode", "num_pages_exceeded"); - logData.put("message", "Number of pages exceeded."); - Gson gson = new Gson(); - String logStr = gson.toJson(logData); - log.warn(" --analytics-- data={}", logStr); - - DocPageCountExceeded progress = new DocPageCountExceeded(pres.getPodId(), pres.getMeetingId(), - pres.getId(), pres.getId(), - pres.getName(), "notUsedYet", "notUsedYet", - pres.isDownloadable(), ConversionMessageConstants.PAGE_COUNT_EXCEEDED_KEY, - e.getPageCount(), e.getMaxNumberOfPages()); - - notifier.sendDocConversionProgress(progress); - } - + public void setPresentationConversionCompletionService(PresentationConversionCompletionService s) { + this.presentationConversionCompletionService = s; } - - private void sendFailedToConvertBigPdfMessage(BigPdfException e, UploadedPresentation pres) { - MessageBuilder builder = new ConversionUpdateMessage.MessageBuilder(pres); - - builder.messageKey(ConversionMessageConstants.PDF_HAS_BIG_PAGE); - - Map logData = new HashMap<>(); - logData.put("podId", pres.getPodId()); - logData.put("meetingId", pres.getMeetingId()); - logData.put("presId", pres.getId()); - logData.put("filename", pres.getName()); - logData.put("pdfSize", pres.getUploadedFile().length()); - logData.put("bigPageNumber", e.getBigPageNumber()); - logData.put("bigPageSize", e.getBigPageSize()); - logData.put("logCode", "big_pdf_has_a_big_page"); - logData.put("message", "The PDF contains a big page."); - Gson gson = new Gson(); - String logStr = gson.toJson(logData); - log.error(" --analytics-- data={}", logStr, e); - - PdfConversionInvalid progress = new PdfConversionInvalid(pres.getPodId(), pres.getMeetingId(), - pres.getId(), pres.getId(), - pres.getName(), "notUsedYet", "notUsedYet", - pres.isDownloadable(), e.getBigPageNumber(), (int)e.getBigPageSize(), - ConversionMessageConstants.PDF_HAS_BIG_PAGE); - - notifier.sendDocConversionProgress(progress); - } - - private void createThumbnails(UploadedPresentation pres) { - notifier.sendCreatingThumbnailsUpdateMessage(pres); - thumbnailCreator.createThumbnails(pres); - } - - private void createTextFiles(UploadedPresentation pres) { - notifier.sendCreatingTextFilesUpdateMessage(pres); - textFileCreator.createTextFiles(pres); - } - - private void createSvgImages(UploadedPresentation pres) { - notifier.sendCreatingSvgImagesUpdateMessage(pres); - svgImageCreator.createSvgImages(pres); - } - - private void createPngImages(UploadedPresentation pres) { - pngCreator.createPng(pres); - } - - private void convertPdfToSwf(UploadedPresentation pres) { - int numPages = pres.getNumberOfPages(); - List slides = setupSlides(pres, numPages); - - CompletionService completionService = new ExecutorCompletionService( - executor); - - generateSlides(pres, slides, completionService); - } - - private void generateSlides(UploadedPresentation pres, - List slides, - CompletionService completionService) { - int slidesCompleted = 0; - - long presConvStart = System.currentTimeMillis(); - - for (final PdfToSwfSlide slide : slides) { - long pageConvStart = System.currentTimeMillis(); - - Callable c = new Callable() { - public PdfToSwfSlide call() { - return slide.createSlide(); - } - }; - - Future f = executor.submit(c); - long endNanos = System.nanoTime() + MAX_CONVERSION_TIME; - try { - // Only wait for the remaining time budget - long timeLeft = endNanos - System.nanoTime(); - PdfToSwfSlide s = f.get(timeLeft, TimeUnit.NANOSECONDS); - slidesCompleted++; - notifier.sendConversionUpdateMessage(slidesCompleted, pres); - } catch (ExecutionException e) { - Map logData = new HashMap<>(); - logData.put("podId", pres.getPodId()); - logData.put("meetingId", pres.getMeetingId()); - logData.put("presId", pres.getId()); - logData.put("filename", pres.getName()); - logData.put("page", slide.getPageNumber()); - logData.put("logCode", "page_conversion_failed"); - logData.put("message", "ExecutionException while converting page."); - Gson gson = new Gson(); - String logStr = gson.toJson(logData); - log.error(" --analytics-- data={}", logStr, e); - } catch (InterruptedException e) { - Map logData = new HashMap<>(); - logData.put("podId", pres.getPodId()); - logData.put("meetingId", pres.getMeetingId()); - logData.put("presId", pres.getId()); - logData.put("filename", pres.getName()); - logData.put("page", slide.getPageNumber()); - logData.put("logCode", "page_conversion_failed"); - logData.put("message", "InterruptedException while converting page"); - Gson gson = new Gson(); - String logStr = gson.toJson(logData); - log.error(" --analytics-- data={}", logStr, e); - - Thread.currentThread().interrupt(); - } catch (TimeoutException e) { - Map logData = new HashMap<>(); - logData.put("podId", pres.getPodId()); - logData.put("meetingId", pres.getMeetingId()); - logData.put("presId", pres.getId()); - logData.put("filename", pres.getName()); - logData.put("page", slide.getPageNumber()); - logData.put("logCode", "page_conversion_failed"); - logData.put("message", "TimeoutException while converting page"); - Gson gson = new Gson(); - String logStr = gson.toJson(logData); - log.error(" --analytics-- data={}", logStr, e); - - f.cancel(true); - } - - long pageConvEnd = System.currentTimeMillis(); - Map logData = new HashMap<>(); - logData.put("podId", pres.getPodId()); - logData.put("meetingId", pres.getMeetingId()); - logData.put("presId", pres.getId()); - logData.put("filename", pres.getName()); - logData.put("page", slide.getPageNumber()); - logData.put("conversionTime(sec)", (pageConvEnd - pageConvStart) / 1000); - logData.put("logCode", "page_conversion_duration"); - logData.put("message", "Page conversion duration(sec)"); - Gson gson = new Gson(); - String logStr = gson.toJson(logData); - log.info(" --analytics-- data={}", logStr); - - } - - for (final PdfToSwfSlide slide : slides) { - if (!slide.isDone()) { - - slide.generateBlankSlide(); - - Map logData = new HashMap<>(); - logData.put("podId", pres.getPodId()); - logData.put("meetingId", pres.getMeetingId()); - logData.put("presId", pres.getId()); - logData.put("filename", pres.getName()); - logData.put("page", slide.getPageNumber()); - logData.put("logCode", "create_blank_slide"); - logData.put("message", "Creating blank slide"); - Gson gson = new Gson(); - String logStr = gson.toJson(logData); - log.warn(" --analytics-- data={}", logStr); - - notifier.sendConversionUpdateMessage(slidesCompleted++, pres); - } - } - - long presConvEnd = System.currentTimeMillis(); - Map logData = new HashMap<>(); - logData.put("podId", pres.getPodId()); - logData.put("meetingId", pres.getMeetingId()); - logData.put("presId", pres.getId()); - logData.put("filename", pres.getName()); - logData.put("conversionTime(sec)", (presConvEnd - presConvStart) / 1000); - logData.put("logCode", "presentation_conversion_duration"); - logData.put("message", "Presentation conversion duration (sec)"); - Gson gson = new Gson(); - String logStr = gson.toJson(logData); - log.info(" --analytics-- data={}", logStr); - - } - - private List setupSlides(UploadedPresentation pres, - int numPages) { - List slides = new ArrayList<>(numPages); - - for (int page = 1; page <= numPages; page++) { - PdfToSwfSlide slide = new PdfToSwfSlide(pres, page); - slide.setBlankSlide(BLANK_SLIDE); - slide.setMaxSwfFileSize(MAX_SWF_FILE_SIZE); - slide.setPageConverter(pdfToSwfConverter); - - slides.add(slide); - } - - return slides; - } - - public void setCounterService(PageCounterService counterService) { - this.counterService = counterService; - } - - 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 void setGeneratePngs(boolean generatePngs) { - this.generatePngs = generatePngs; - } - - public void setSwfSlidesRequired(boolean swfSlidesRequired) { - this.swfSlidesRequired = swfSlidesRequired; - } - - public void setBigPdfSize(long bigPdfSize) { - this.bigPdfSize = bigPdfSize; - } - - public void setMaxBigPdfPageSize(long maxBigPdfPageSize) { - this.maxBigPdfPageSize = maxBigPdfPageSize; - } - - public void setPageExtractor(PageExtractor extractor) { - this.pageExtractor = extractor; - } - - public void setSvgImagesRequired(boolean svgImagesRequired) { - this.svgImagesRequired = svgImagesRequired; - } - - public void setThumbnailCreator(ThumbnailCreator thumbnailCreator) { - this.thumbnailCreator = thumbnailCreator; - } - - public void setPngCreator(PngCreator pngCreator) { - this.pngCreator = pngCreator; - } - - public void setTextFileCreator(TextFileCreator textFileCreator) { - this.textFileCreator = textFileCreator; - } - - public void setSvgImageCreator(SvgImageCreator svgImageCreator) { - this.svgImageCreator = svgImageCreator; - } - - public void setMaxConversionTime(int minutes) { - MAX_CONVERSION_TIME = minutes * 60 * 1000L * 1000L * 1000L; - } - - public void setSwfSlidesGenerationProgressNotifier( - SwfSlidesGenerationProgressNotifier notifier) { - this.notifier = notifier; - } - } diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/Png2SwfPageConverter.java b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/Png2SwfPageConverter.java index 2a0fa04760b4..d7324368befe 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/Png2SwfPageConverter.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/Png2SwfPageConverter.java @@ -38,7 +38,7 @@ public class Png2SwfPageConverter implements PageConverter { public boolean convert(File presentationFile, File output, int page, UploadedPresentation pres){ String COMMAND = SWFTOOLS_DIR + File.separatorChar + "png2swf -o " + output.getAbsolutePath() + " " + presentationFile.getAbsolutePath(); - boolean done = new ExternalProcessExecutor().exec(COMMAND, 60000); + boolean done = new ExternalProcessExecutor().exec(COMMAND, 10000); if (done && output.exists()) { return true; diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/PngCreatorImp.java b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/PngCreatorImp.java index a0bcd56f169d..cb2c54f76cd0 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/PngCreatorImp.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/PngCreatorImp.java @@ -19,62 +19,104 @@ package org.bigbluebutton.presentation.imp; import com.google.gson.Gson; +import com.zaxxer.nuprocess.NuProcess; +import com.zaxxer.nuprocess.NuProcessBuilder; import org.apache.commons.io.FileUtils; import org.bigbluebutton.presentation.PngCreator; +import org.bigbluebutton.presentation.SupportedFileTypes; import org.bigbluebutton.presentation.UploadedPresentation; +import org.bigbluebutton.presentation.handlers.Png2SvgConversionHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; +import java.util.Arrays; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; public class PngCreatorImp implements PngCreator { private static Logger log = LoggerFactory.getLogger(PngCreatorImp.class); - private static final Pattern PAGE_NUMBER_PATTERN = Pattern.compile("(.+-png)-([0-9]+)(.png)"); + private static final Pattern PAGE_NUMBER_PATTERN = Pattern.compile("(.+-png)-([0-9]+)-([0-9]+)(.png)"); private String BLANK_PNG; private int slideWidth = 800; + private String convTimeout = "7s"; + private int WAIT_FOR_SEC = 7; private static final String TEMP_PNG_NAME = "temp-png"; - public boolean createPng(UploadedPresentation pres) { + public boolean createPng(UploadedPresentation pres, int page, File pageFile) { boolean success = false; File pngDir = determinePngDirectory(pres.getUploadedFile()); if (!pngDir.exists()) pngDir.mkdir(); - cleanDirectory(pngDir); - try { - success = generatePngs(pngDir, pres); + long start = System.currentTimeMillis(); + success = generatePng(pngDir, pres, page, pageFile); + long end = System.currentTimeMillis(); + //System.out.println("*** GENERATE PNG " + (end - start)); } catch (InterruptedException e) { log.warn("Interrupted Exception while generating png."); success = false; } + long start = System.currentTimeMillis(); + renamePng(pngDir, page); // Create blank thumbnails for pages that failed to generate a thumbnail. - createBlankPngs(pngDir, pres.getNumberOfPages()); + createBlankPng(pngDir, page); + long end = System.currentTimeMillis(); + //System.out.println("*** GENERATE BLANK PNG " + (end - start)); - renamePng(pngDir); + //start = System.currentTimeMillis(); + //renamePng(pngDir); + //end = System.currentTimeMillis(); + //System.out.println("*** RENAME PNG " + (end - start)); return success; } - private boolean generatePngs(File pngsDir, UploadedPresentation pres) + private boolean generatePng(File pngsDir, UploadedPresentation pres, int page, File pageFile) throws InterruptedException { - String source = pres.getUploadedFile().getAbsolutePath(); + String source = pageFile.getAbsolutePath(); String dest; + + if (SupportedFileTypes.isImageFile(pres.getFileType())) { + // Need to create a PDF as intermediate step. + // Convert single image file + dest = pngsDir.getAbsolutePath() + File.separator + "slide-1.pdf"; + + NuProcessBuilder convertImgToSvg = new NuProcessBuilder( + Arrays.asList("timeout", convTimeout, "convert", source, "-auto-orient", dest)); + + Png2SvgConversionHandler pHandler = new Png2SvgConversionHandler(); + convertImgToSvg.setProcessListener(pHandler); + + NuProcess process = convertImgToSvg.start(); + try { + process.waitFor(WAIT_FOR_SEC, TimeUnit.SECONDS); + } catch (InterruptedException e) { + log.error("InterruptedException while converting to PDF {}", dest, e); + return false; + } + + // Use the intermediate PDF file as source + source = dest; + } + String COMMAND = ""; - dest = pngsDir.getAbsolutePath() + File.separator + TEMP_PNG_NAME; + dest = pngsDir.getAbsolutePath() + File.separator + TEMP_PNG_NAME + "-" + page; // the "-x.png" is appended automagically COMMAND = "pdftocairo -png -scale-to " + slideWidth + " " + source + " " + dest; - boolean done = new ExternalProcessExecutor().exec(COMMAND, 60000); + //System.out.println("********* CREATING PNGs " + COMMAND); + + boolean done = new ExternalProcessExecutor().exec(COMMAND, 10000); if (done) { return true; @@ -98,7 +140,7 @@ private File determinePngDirectory(File presentationFile) { return new File(presentationFile.getParent() + File.separatorChar + "pngs"); } - private void renamePng(File dir) { + private void renamePng(File dir, int page) { /* * If more than 1 file, filename like 'temp-png-X.png' else filename is * 'temp-png.png' @@ -107,6 +149,9 @@ private void renamePng(File dir) { File[] files = dir.listFiles(); Matcher matcher; for (int i = 0; i < files.length; i++) { + + //System.out.println("*** PPNG file " + files[i].getAbsolutePath()); + matcher = PAGE_NUMBER_PATTERN.matcher(files[i].getAbsolutePath()); if (matcher.matches()) { // Path should be something like @@ -118,15 +163,19 @@ private void renamePng(File dir) { // 3. .png // We are interested in the second match. int pageNum = Integer.parseInt(matcher.group(2).trim()); - String newFilename = "slide-" + (pageNum) + ".png"; - File renamedFile = new File( - dir.getAbsolutePath() + File.separator + newFilename); - files[i].renameTo(renamedFile); + if (pageNum == page) { + String newFilename = "slide-" + (page) + ".png"; + File renamedFile = new File( + dir.getAbsolutePath() + File.separator + newFilename); + files[i].renameTo(renamedFile); + } + } } } else if (dir.list().length == 1) { File oldFilename = new File( dir.getAbsolutePath() + File.separator + dir.list()[0]); + //System.out.println("*** PPNG file " + oldFilename.getAbsolutePath()); String newFilename = "slide-1.png"; File renamedFile = new File( oldFilename.getParent() + File.separator + newFilename); @@ -134,17 +183,11 @@ private void renamePng(File dir) { } } - private void createBlankPngs(File pngsDir, int pageCount) { - File[] pngs = pngsDir.listFiles(); - - if (pngs.length != pageCount) { - for (int i = 0; i < pageCount; i++) { - File png = new File(pngsDir.getAbsolutePath() + File.separator + TEMP_PNG_NAME + "-" + i + ".png"); - if (!png.exists()) { - log.info("Copying blank png for slide {}", i); - copyBlankPng(png); - } - } + private void createBlankPng(File pngsDir, int page) { + File png = new File(pngsDir.getAbsolutePath() + File.separator + "slide-" + page + ".png"); + if (!png.exists()) { + log.info("Copying blank png for slide {}", page); + copyBlankPng(png); } } @@ -152,7 +195,7 @@ private void copyBlankPng(File png) { try { FileUtils.copyFile(new File(BLANK_PNG), png); } catch (IOException e) { - log.error("IOException while copying blank thumbnail."); + log.error("IOException while copying blank PNG."); } } diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/PresentationConversionCompletionService.java b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/PresentationConversionCompletionService.java new file mode 100755 index 000000000000..ce6cd45eae54 --- /dev/null +++ b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/PresentationConversionCompletionService.java @@ -0,0 +1,104 @@ +package org.bigbluebutton.presentation.imp; + +import com.google.gson.Gson; +import org.bigbluebutton.presentation.messages.IPresentationCompletionMessage; +import org.bigbluebutton.presentation.messages.PageConvertProgressMessage; +import org.bigbluebutton.presentation.messages.PresentationConvertMessage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.*; + +public class PresentationConversionCompletionService { + private static Logger log = LoggerFactory.getLogger(PresentationConversionCompletionService.class); + + private SwfSlidesGenerationProgressNotifier notifier; + + private ExecutorService executor; + private volatile boolean processProgress = false; + + private final ConcurrentMap presentationsToConvert + = new ConcurrentHashMap(); + + private BlockingQueue messages = new LinkedBlockingQueue(); + + public PresentationConversionCompletionService() { + executor = Executors.newSingleThreadExecutor(); + } + + public void handle(IPresentationCompletionMessage msg) { + messages.offer(msg); + } + + private void processMessage(IPresentationCompletionMessage msg) { + if (msg instanceof PresentationConvertMessage) { + PresentationConvertMessage m = (PresentationConvertMessage) msg; + PresentationToConvert p = new PresentationToConvert(m.pres); + presentationsToConvert.put(p.getKey(), p); + } else if (msg instanceof PageConvertProgressMessage) { + + PageConvertProgressMessage m = (PageConvertProgressMessage) msg; + PresentationToConvert p = presentationsToConvert.get(m.presId); + if (p != null) { + p.incrementPagesCompleted(); + notifier.sendConversionUpdateMessage(p.getPagesCompleted(), p.pres, m.page); + if (p.getPagesCompleted() == p.pres.getNumberOfPages()) { + handleEndProcessing(p); + } + } + } + } + + private void handleEndProcessing(PresentationToConvert p) { + presentationsToConvert.remove(p.getKey()); + + Map logData = new HashMap(); + logData = new HashMap(); + logData.put("podId", p.pres.getPodId()); + logData.put("meetingId", p.pres.getMeetingId()); + logData.put("presId", p.pres.getId()); + logData.put("filename", p.pres.getName()); + logData.put("current", p.pres.isCurrent()); + logData.put("logCode", "presentation_conversion_end"); + logData.put("message", "End presentation conversion."); + + Gson gson = new Gson(); + String logStr = gson.toJson(logData); + log.info(" --analytics-- data={}", logStr); + + notifier.sendConversionCompletedMessage(p.pres); + } + public void start() { + log.info("Ready to process presentation files!"); + + try { + processProgress = true; + + Runnable messageProcessor = new Runnable() { + public void run() { + while (processProgress) { + try { + IPresentationCompletionMessage msg = messages.take(); + processMessage(msg); + } catch (InterruptedException e) { + log.warn("Error while taking presentation file from queue."); + } + } + } + }; + executor.submit(messageProcessor); + } catch (Exception e) { + log.error("Error processing presentation file: {}", e); + } + } + + public void stop() { + processProgress = false; + } + + public void setSwfSlidesGenerationProgressNotifier(SwfSlidesGenerationProgressNotifier notifier) { + this.notifier = notifier; + } +} diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/PresentationFileProcessor.java b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/PresentationFileProcessor.java new file mode 100755 index 000000000000..6a509c2bb54a --- /dev/null +++ b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/PresentationFileProcessor.java @@ -0,0 +1,328 @@ +package org.bigbluebutton.presentation.imp; + +import com.google.gson.Gson; +import org.bigbluebutton.presentation.*; +import org.bigbluebutton.presentation.messages.DocPageConversionStarted; +import org.bigbluebutton.presentation.messages.DocPageCountExceeded; +import org.bigbluebutton.presentation.messages.DocPageCountFailed; +import org.bigbluebutton.presentation.messages.PresentationConvertMessage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.LinkedBlockingQueue; + +public class PresentationFileProcessor { + private static Logger log = LoggerFactory.getLogger(PresentationFileProcessor.class); + + private boolean swfSlidesRequired; + private boolean svgImagesRequired; + private boolean generatePngs; + private PageExtractor pageExtractor; + + private String BLANK_SLIDE; + private int MAX_SWF_FILE_SIZE; + private long bigPdfSize; + private long maxBigPdfPageSize; + + private long MAX_CONVERSION_TIME = 5 * 60 * 1000L; + + private TextFileCreator textFileCreator; + private SvgImageCreator svgImageCreator; + private ThumbnailCreator thumbnailCreator; + private PngCreator pngCreator; + private PageConverter pdfToSwfConverter; + private SwfSlidesGenerationProgressNotifier notifier; + private PageCounterService counterService; + private PresentationConversionCompletionService presentationConversionCompletionService; + private ImageToSwfSlidesGenerationService imageToSwfSlidesGenerationService; + private PdfToSwfSlidesGenerationService pdfToSwfSlidesGenerationService; + + private ExecutorService executor; + private volatile boolean processPresentation = false; + + private BlockingQueue presentations = new LinkedBlockingQueue(); + + public PresentationFileProcessor(int numConversionThreads) { + executor = Executors.newFixedThreadPool(numConversionThreads); + } + + public synchronized void process(UploadedPresentation pres) { + Runnable messageProcessor = new Runnable() { + public void run() { + processUploadedPresentation(pres); + } + }; + executor.submit(messageProcessor); + } + + private void processUploadedPresentation(UploadedPresentation pres) { + if (SupportedFileTypes.isPdfFile(pres.getFileType())) { + determineNumberOfPages(pres); + sendDocPageConversionStartedProgress(pres); + PresentationConvertMessage msg = new PresentationConvertMessage(pres); + presentationConversionCompletionService.handle(msg); + extractIntoPages(pres); + } else if (SupportedFileTypes.isImageFile(pres.getFileType())) { + pres.setNumberOfPages(1); // There should be only one image to convert. + sendDocPageConversionStartedProgress(pres); + imageToSwfSlidesGenerationService.generateSlides(pres); + } + } + + private void extractIntoPages(UploadedPresentation pres) { + for (int page = 1; page <= pres.getNumberOfPages(); page++) { + String presDir = pres.getUploadedFile().getParent(); + File pageFile = new File(presDir + "/page" + "-" + page + ".pdf"); + + File extractedPageFile = extractPage(pres, page); + + if (extractedPageFile.length() > maxBigPdfPageSize) { + File downscaledPageFile = downscalePage(pres, extractedPageFile, page); + downscaledPageFile.renameTo(pageFile); + extractedPageFile.delete(); + } else { + extractedPageFile.renameTo(pageFile); + } + + PageToConvert pageToConvert = new PageToConvert( + pres, + page, + pageFile, + swfSlidesRequired, + svgImagesRequired, + generatePngs, + textFileCreator, + svgImageCreator, + thumbnailCreator, + pngCreator, + pdfToSwfConverter, + notifier, + BLANK_SLIDE, + MAX_SWF_FILE_SIZE + ); + + pdfToSwfSlidesGenerationService.process(pageToConvert); + } + } + + private File downscalePage(UploadedPresentation pres, File filePage, int pageNum) { + String presDir = pres.getUploadedFile().getParent(); + File tempPage = new File(presDir + "/downscaled" + "-" + pageNum + ".pdf"); + PdfPageDownscaler downscaler = new PdfPageDownscaler(); + downscaler.downscale(filePage, tempPage); + if (tempPage.exists()) { + return tempPage; + } + + return filePage; + } + + private File extractPage(UploadedPresentation pres, int page) { + String presDir = pres.getUploadedFile().getParent(); + + File tempPage = new File(presDir + "/extracted" + "-" + page + ".pdf"); + pageExtractor.extractPage(pres.getUploadedFile(), tempPage, page); + + return tempPage; + } + + private boolean determineNumberOfPages(UploadedPresentation pres) { + try { + counterService.determineNumberOfPages(pres); + return true; + } catch (CountingPageException e) { + sendFailedToCountPageMessage(e, pres); + } + return false; + } + + private void sendDocPageConversionStartedProgress(UploadedPresentation pres) { + Map logData = new HashMap(); + + logData.put("podId", pres.getPodId()); + logData.put("meetingId", pres.getMeetingId()); + logData.put("presId", pres.getId()); + logData.put("filename", pres.getName()); + logData.put("num_pages", pres.getNumberOfPages()); + logData.put("authzToken", pres.getAuthzToken()); + logData.put("logCode", "presentation_conversion_num_pages"); + logData.put("message", "Presentation conversion number of pages."); + + Gson gson = new Gson(); + String logStr = gson.toJson(logData); + log.info(" --analytics-- data={}", logStr); + + DocPageConversionStarted progress = new DocPageConversionStarted( + pres.getPodId(), + pres.getMeetingId(), + pres.getId(), + pres.getName(), + pres.getAuthzToken(), + pres.isDownloadable(), + pres.isCurrent(), + pres.getNumberOfPages()); + notifier.sendDocConversionProgress(progress); + } + + private void sendFailedToCountPageMessage(CountingPageException e, UploadedPresentation pres) { + ConversionUpdateMessage.MessageBuilder builder = new ConversionUpdateMessage.MessageBuilder(pres); + + if (e.getExceptionType() == CountingPageException.ExceptionType.PAGE_COUNT_EXCEPTION) { + builder.messageKey(ConversionMessageConstants.PAGE_COUNT_FAILED_KEY); + + Map logData = new HashMap<>(); + logData.put("podId", pres.getPodId()); + logData.put("meetingId", pres.getMeetingId()); + logData.put("presId", pres.getId()); + logData.put("filename", pres.getName()); + logData.put("logCode", "determine_num_pages_failed"); + logData.put("message", "Failed to determine number of pages."); + Gson gson = new Gson(); + String logStr = gson.toJson(logData); + log.error(" --analytics-- data={}", logStr, e); + + DocPageCountFailed progress = new DocPageCountFailed(pres.getPodId(), pres.getMeetingId(), + pres.getId(), pres.getId(), + pres.getName(), "notUsedYet", "notUsedYet", + pres.isDownloadable(), ConversionMessageConstants.PAGE_COUNT_FAILED_KEY); + + notifier.sendDocConversionProgress(progress); + + } else if (e.getExceptionType() == CountingPageException.ExceptionType.PAGE_EXCEEDED_EXCEPTION) { + builder.numberOfPages(e.getPageCount()); + builder.maxNumberPages(e.getMaxNumberOfPages()); + builder.messageKey(ConversionMessageConstants.PAGE_COUNT_EXCEEDED_KEY); + + Map logData = new HashMap(); + logData.put("podId", pres.getPodId()); + logData.put("meetingId", pres.getMeetingId()); + logData.put("presId", pres.getId()); + logData.put("filename", pres.getName()); + logData.put("pageCount", e.getPageCount()); + logData.put("maxNumPages", e.getMaxNumberOfPages()); + logData.put("logCode", "num_pages_exceeded"); + logData.put("message", "Number of pages exceeded."); + Gson gson = new Gson(); + String logStr = gson.toJson(logData); + log.warn(" --analytics-- data={}", logStr); + + DocPageCountExceeded progress = new DocPageCountExceeded(pres.getPodId(), pres.getMeetingId(), + pres.getId(), pres.getId(), + pres.getName(), "notUsedYet", "notUsedYet", + pres.isDownloadable(), ConversionMessageConstants.PAGE_COUNT_EXCEEDED_KEY, + e.getPageCount(), e.getMaxNumberOfPages()); + + notifier.sendDocConversionProgress(progress); + } + } + + public void start() { + log.info("Ready to process presentation files!"); + + try { + processPresentation = true; + + Runnable messageProcessor = new Runnable() { + public void run() { + while (processPresentation) { + try { + UploadedPresentation pres = presentations.take(); + processUploadedPresentation(pres); + } catch (InterruptedException e) { + log.warn("Error while taking presentation file from queue."); + } + } + } + }; + executor.submit(messageProcessor); + } catch (Exception e) { + log.error("Error processing presentation file: {}", e); + } + } + + public void stop() { + processPresentation = false; + } + + public void setSwfSlidesGenerationProgressNotifier(SwfSlidesGenerationProgressNotifier notifier) { + this.notifier = notifier; + } + + public void setCounterService(PageCounterService counterService) { + this.counterService = counterService; + } + + public void setPageExtractor(PageExtractor extractor) { + this.pageExtractor = extractor; + } + + 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 void setGeneratePngs(boolean generatePngs) { + this.generatePngs = generatePngs; + } + + public void setSwfSlidesRequired(boolean swfSlidesRequired) { + this.swfSlidesRequired = swfSlidesRequired; + } + + public void setBigPdfSize(long bigPdfSize) { + this.bigPdfSize = bigPdfSize; + } + + public void setMaxBigPdfPageSize(long maxBigPdfPageSize) { + this.maxBigPdfPageSize = maxBigPdfPageSize; + } + + public void setSvgImagesRequired(boolean svgImagesRequired) { + this.svgImagesRequired = svgImagesRequired; + } + + public void setThumbnailCreator(ThumbnailCreator thumbnailCreator) { + this.thumbnailCreator = thumbnailCreator; + } + + public void setPngCreator(PngCreator pngCreator) { + this.pngCreator = pngCreator; + } + + public void setTextFileCreator(TextFileCreator textFileCreator) { + this.textFileCreator = textFileCreator; + } + + public void setSvgImageCreator(SvgImageCreator svgImageCreator) { + this.svgImageCreator = svgImageCreator; + } + + public void setMaxConversionTime(int minutes) { + MAX_CONVERSION_TIME = minutes * 60 * 1000L * 1000L * 1000L; + } + + public void setImageToSwfSlidesGenerationService(ImageToSwfSlidesGenerationService s) { + imageToSwfSlidesGenerationService = s; + } + + public void setPresentationConversionCompletionService(PresentationConversionCompletionService s) { + this.presentationConversionCompletionService = s; + } + + public void setPdfToSwfSlidesGenerationService(PdfToSwfSlidesGenerationService s) { + this.pdfToSwfSlidesGenerationService = s; + } +} diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/PresentationToConvert.java b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/PresentationToConvert.java new file mode 100755 index 000000000000..d4a8bcef7fdf --- /dev/null +++ b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/PresentationToConvert.java @@ -0,0 +1,24 @@ +package org.bigbluebutton.presentation.imp; + +import org.bigbluebutton.presentation.UploadedPresentation; + +public class PresentationToConvert { + public final UploadedPresentation pres; + private int pagesCompleted = 0; + + public PresentationToConvert(UploadedPresentation pres) { + this.pres = pres; + } + + public String getKey() { + return pres.getId(); + } + + public int getPagesCompleted() { + return pagesCompleted; + } + + public void incrementPagesCompleted() { + pagesCompleted++; + } +} diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/SvgImageCreatorImp.java b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/SvgImageCreatorImp.java index cafeb3835d11..b52a7c00f4de 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/SvgImageCreatorImp.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/SvgImageCreatorImp.java @@ -31,17 +31,17 @@ public class SvgImageCreatorImp implements SvgImageCreator { private long pathsThreshold; private String convTimeout = "7s"; private int WAIT_FOR_SEC = 7; + private String BLANK_SVG; @Override - public boolean createSvgImages(UploadedPresentation pres) { + public boolean createSvgImage(UploadedPresentation pres, int page) { boolean success = false; File svgImagesPresentationDir = determineSvgImagesDirectory(pres.getUploadedFile()); if (!svgImagesPresentationDir.exists()) svgImagesPresentationDir.mkdir(); try { - FileUtils.cleanDirectory(svgImagesPresentationDir); - success = generateSvgImages(svgImagesPresentationDir, pres); + success = generateSvgImage(svgImagesPresentationDir, pres, page); } catch (Exception e) { log.error("Interrupted Exception while generating images {}", pres.getName(), e); success = false; @@ -50,18 +50,18 @@ public boolean createSvgImages(UploadedPresentation pres) { return success; } - private boolean generateSvgImages(File imagePresentationDir, UploadedPresentation pres) + private boolean generateSvgImage(File imagePresentationDir, UploadedPresentation pres, int page) throws InterruptedException { String source = pres.getUploadedFile().getAbsolutePath(); String dest; - int numSlides; + + int numSlides = 1; boolean done = false; - int slidesCompleted = 0; - + // Convert single image file if (SupportedFileTypes.isImageFile(pres.getFileType())) { - numSlides = 1; - dest = imagePresentationDir.getAbsolutePath() + File.separator + "slide1.pdf"; + + dest = imagePresentationDir.getAbsolutePath() + File.separator + "slide-1.pdf"; NuProcessBuilder convertImgToSvg = new NuProcessBuilder( Arrays.asList("timeout", convTimeout, "convert", source, "-auto-orient", dest)); @@ -78,134 +78,138 @@ private boolean generateSvgImages(File imagePresentationDir, UploadedPresentatio log.error("InterruptedException while converting to SVG {}", dest, e); } - source = imagePresentationDir.getAbsolutePath() + File.separator + "slide1.pdf"; - } else { - numSlides = pres.getNumberOfPages(); + // Use the intermediate PDF file as source + source = dest; } + //System.out.println("******** CREATING SVG page "); + // Continue image processing - for (int i = 1; i <= numSlides; i++) { - File destsvg = new File(imagePresentationDir.getAbsolutePath() + File.separatorChar + "slide" + i + ".svg"); + long startConv = System.currentTimeMillis(); + + File destsvg = new File(imagePresentationDir.getAbsolutePath() + File.separatorChar + "slide" + page + ".svg"); - NuProcessBuilder convertPdfToSvg = createConversionProcess("-svg", i, source, destsvg.getAbsolutePath(), + NuProcessBuilder convertPdfToSvg = createConversionProcess("-svg", page, source, destsvg.getAbsolutePath(), true); - SvgConversionHandler pHandler = new SvgConversionHandler(); - convertPdfToSvg.setProcessListener(pHandler); + SvgConversionHandler pHandler = new SvgConversionHandler(); + convertPdfToSvg.setProcessListener(pHandler); - NuProcess process = convertPdfToSvg.start(); - try { - process.waitFor(WAIT_FOR_SEC, TimeUnit.SECONDS); - done = true; - } catch (InterruptedException e) { - log.error("Interrupted Exception while generating SVG slides {}", pres.getName(), e); - } - if (!done) { - break; - } + NuProcess process = convertPdfToSvg.start(); + try { + process.waitFor(WAIT_FOR_SEC, TimeUnit.SECONDS); + done = true; + } catch (InterruptedException e) { + log.error("Interrupted Exception while generating SVG slides {}", pres.getName(), e); + } - if (destsvg.length() == 0 || pHandler.numberOfImageTags() > imageTagThreshold - || pHandler.numberOfPaths() > pathsThreshold) { - // We need t delete the destination file as we are starting a - // new conversion process - if (destsvg.exists()) { - destsvg.delete(); - } + if (!done) { + return done; + } - done = false; + if (destsvg.length() == 0 || pHandler.numberOfImageTags() > imageTagThreshold + || pHandler.numberOfPaths() > pathsThreshold) { + // We need t delete the destination file as we are starting a + // new conversion process + if (destsvg.exists()) { + destsvg.delete(); + } - Map logData = new HashMap(); + done = false; + + Map 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", destsvg.exists()); + logData.put("numberOfImages", pHandler.numberOfImageTags()); + logData.put("numberOfPaths", pHandler.numberOfPaths()); + logData.put("logCode", "potential_problem_with_svg"); + logData.put("message", "Potential problem with generated SVG"); + 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("page", i); - logData.put("convertSuccess", pHandler.isCommandSuccessful()); - logData.put("fileExists", destsvg.exists()); - logData.put("numberOfImages", pHandler.numberOfImageTags()); - logData.put("numberOfPaths", pHandler.numberOfPaths()); - logData.put("logCode", "potential_problem_with_svg"); - logData.put("message", "Potential problem with generated SVG"); - 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 + "-" + i, ".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", "problem_with_creating_svg"); - logData.put("message", "Unable to create temporary files"); - gson = new Gson(); - logStr = gson.toJson(logData); - log.error(" --analytics-- data={}", logStr, ioException); - } - - // Step 1: Convert a PDF page to PNG using a raw pdftocairo - NuProcessBuilder convertPdfToPng = createConversionProcess("-png", i, source, + logData.put("logCode", "problem_with_creating_svg"); + logData.put("message", "Unable to create temporary files"); + gson = new Gson(); + logStr = gson.toJson(logData); + log.error(" --analytics-- data={}", logStr, ioException); + } + + // Step 1: Convert a PDF page to PNG using a raw pdftocairo + NuProcessBuilder convertPdfToPng = createConversionProcess("-png", page, source, tempPng.getAbsolutePath().substring(0, tempPng.getAbsolutePath().lastIndexOf('.')), false); - Pdf2PngPageConverterHandler pngHandler = new Pdf2PngPageConverterHandler(); - convertPdfToPng.setProcessListener(pngHandler); - NuProcess pngProcess = convertPdfToPng.start(); - try { - pngProcess.waitFor(WAIT_FOR_SEC, TimeUnit.SECONDS); - } catch (InterruptedException e) { - log.error("Interrupted Exception while generating PNG image {}", pres.getName(), e); - } - - // Step 2: Convert a PNG image to SVG - NuProcessBuilder convertPngToSvg = new NuProcessBuilder(Arrays.asList("timeout", convTimeout, "convert", + Pdf2PngPageConverterHandler pngHandler = new Pdf2PngPageConverterHandler(); + convertPdfToPng.setProcessListener(pngHandler); + NuProcess pngProcess = convertPdfToPng.start(); + try { + pngProcess.waitFor(WAIT_FOR_SEC, TimeUnit.SECONDS); + } catch (InterruptedException e) { + log.error("Interrupted Exception while generating PNG image {}", pres.getName(), e); + } + + // Step 2: Convert a PNG image to SVG + NuProcessBuilder convertPngToSvg = new NuProcessBuilder(Arrays.asList("timeout", convTimeout, "convert", tempPng.getAbsolutePath(), destsvg.getAbsolutePath())); - Png2SvgConversionHandler svgHandler = new Png2SvgConversionHandler(); - convertPngToSvg.setProcessListener(svgHandler); - NuProcess svgProcess = convertPngToSvg.start(); - try { - svgProcess.waitFor(WAIT_FOR_SEC, TimeUnit.SECONDS); - } catch (InterruptedException e) { - log.error("Interrupted Exception while generating SVG image {}", pres.getName(), e); - } + Png2SvgConversionHandler svgHandler = new Png2SvgConversionHandler(); + convertPngToSvg.setProcessListener(svgHandler); + NuProcess svgProcess = convertPngToSvg.start(); + try { + svgProcess.waitFor(WAIT_FOR_SEC, TimeUnit.SECONDS); + } catch (InterruptedException e) { + log.error("Interrupted Exception while generating SVG image {}", pres.getName(), e); + } - done = svgHandler.isCommandSuccessful(); + done = svgHandler.isCommandSuccessful(); - // Delete the temporary PNG after finishing the image conversion - tempPng.delete(); + // Delete the temporary PNG after finishing the image conversion + tempPng.delete(); - // Step 3: Add SVG namespace to the destionation file - // Check : https://phabricator.wikimedia.org/T43174 - NuProcessBuilder addNameSpaceToSVG = new NuProcessBuilder(Arrays.asList("timeout", convTimeout, + // Step 3: Add SVG namespace to the destionation file + // Check : https://phabricator.wikimedia.org/T43174 + NuProcessBuilder addNameSpaceToSVG = new NuProcessBuilder(Arrays.asList("timeout", convTimeout, "/bin/sh", "-c", "sed -i " + "'4s|>| xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" version=\"1.2\">|' " + destsvg.getAbsolutePath())); - AddNamespaceToSvgHandler namespaceHandler = new AddNamespaceToSvgHandler(); - addNameSpaceToSVG.setProcessListener(namespaceHandler); - NuProcess namespaceProcess = addNameSpaceToSVG.start(); - try { - namespaceProcess.waitFor(WAIT_FOR_SEC, TimeUnit.SECONDS); - } catch (InterruptedException e) { - log.error("Interrupted Exception while adding SVG namespace {}", pres.getName(), e); - } + AddNamespaceToSvgHandler namespaceHandler = new AddNamespaceToSvgHandler(); + addNameSpaceToSVG.setProcessListener(namespaceHandler); + NuProcess namespaceProcess = addNameSpaceToSVG.start(); + try { + namespaceProcess.waitFor(WAIT_FOR_SEC, TimeUnit.SECONDS); + } catch (InterruptedException e) { + log.error("Interrupted Exception while adding SVG namespace {}", pres.getName(), e); } - - slidesCompleted++; - notifier.sendConversionUpdateMessage(slidesCompleted, pres); - } + long endConv = System.currentTimeMillis(); + + //System.out.println("******** CREATING SVG page " + page + " " + (endConv - startConv)); + if (done) { return true; } + copyBlankSvgs(imagePresentationDir, pres.getNumberOfPages()); + Map logData = new HashMap(); logData.put("meetingId", pres.getMeetingId()); logData.put("presId", pres.getId()); @@ -235,6 +239,33 @@ private File determineSvgImagesDirectory(File presentationFile) { return new File(presentationFile.getParent() + File.separatorChar + "svgs"); } + private void copyBlankSvgs(File svgssDir, int pageCount) { + File[] svgs = svgssDir.listFiles(); + + if (svgs.length != pageCount) { + for (int i = 1; i <= pageCount; i++) { + File svg = new File(svgssDir.getAbsolutePath() + File.separator + "slide" + i + ".svg"); + if (!svg.exists()) { + log.info("Copying blank svg for slide {}", i); + copyBlankSvg(svg); + } + } + } + } + + private void copyBlankSvg(File svg) { + try { + FileUtils.copyFile(new File(BLANK_SVG), svg); + } catch (IOException e) { + log.error("IOException while copying blank SVG."); + } + } + + + public void setBlankSvg(String blankSvg) { + BLANK_SVG = blankSvg; + } + public void setImageTagThreshold(long threshold) { imageTagThreshold = threshold; } diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/SwfSlidesGenerationProgressNotifier.java b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/SwfSlidesGenerationProgressNotifier.java index 3039d37e0a6a..00206efb1286 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/SwfSlidesGenerationProgressNotifier.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/SwfSlidesGenerationProgressNotifier.java @@ -42,12 +42,21 @@ public void sendDocConversionProgress(IDocConversionMsg msg) { } - public void sendConversionUpdateMessage(int slidesCompleted, UploadedPresentation pres) { - DocPageGeneratedProgress progress = new DocPageGeneratedProgress(pres.getPodId(), pres.getMeetingId(), - pres.getId(), pres.getId(), - pres.getName(), "notUsedYet", "notUsedYet", - pres.isDownloadable(), ConversionMessageConstants.GENERATED_SLIDE_KEY, - pres.getNumberOfPages(), slidesCompleted); + public void sendConversionUpdateMessage(int slidesCompleted, UploadedPresentation pres, int pageGenerated) { + DocPageGeneratedProgress progress = new DocPageGeneratedProgress(pres.getPodId(), + pres.getMeetingId(), + pres.getId(), + pres.getId(), + pres.getName(), + "notUsedYet", + "notUsedYet", + pres.isDownloadable(), + ConversionMessageConstants.GENERATED_SLIDE_KEY, + pres.getNumberOfPages(), + slidesCompleted, + generateBasePresUrl(pres), + pageGenerated, + (pageGenerated == 1)); messagingService.sendDocConversionMsg(progress); } diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/TextFileCreatorImp.java b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/TextFileCreatorImp.java index 4763fb3b8aa1..7fabc20697f7 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/TextFileCreatorImp.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/TextFileCreatorImp.java @@ -39,16 +39,15 @@ public class TextFileCreatorImp implements TextFileCreator { private static Logger log = LoggerFactory.getLogger(TextFileCreatorImp.class); @Override - public boolean createTextFiles(UploadedPresentation pres) { + public boolean createTextFile(UploadedPresentation pres, int page) { boolean success = false; File textfilesDir = determineTextfilesDirectory(pres.getUploadedFile()); if (!textfilesDir.exists()) textfilesDir.mkdir(); - cleanDirectory(textfilesDir); try { - success = generateTextFiles(textfilesDir, pres); + success = generateTextFile(textfilesDir, pres, page); } catch (InterruptedException e) { log.error("Interrupted Exception while generating thumbnails {}", pres.getName(), e); success = false; @@ -61,8 +60,8 @@ public boolean createTextFiles(UploadedPresentation pres) { return success; } - private boolean generateTextFiles(File textfilesDir, - UploadedPresentation pres) throws InterruptedException { + private boolean generateTextFile(File textfilesDir, + UploadedPresentation pres, int page) throws InterruptedException { boolean success = true; String source = pres.getUploadedFile().getAbsolutePath(); String dest; @@ -90,11 +89,14 @@ private boolean generateTextFiles(File textfilesDir, } } else { - dest = textfilesDir.getAbsolutePath() + File.separatorChar + "slide-"; + dest = textfilesDir.getAbsolutePath() + File.separatorChar + "slide-" + page + ".txt"; // sudo apt-get install xpdf-utils - for (int i = 1; i <= pres.getNumberOfPages(); i++) { - COMMAND = "pdftotext -raw -nopgbrk -enc UTF-8 -f " + i + " -l " + i - + " " + source + " " + dest + i + ".txt"; + + COMMAND = "pdftotext -raw -nopgbrk -enc UTF-8 -f " + page + " -l " + page + + " " + source + " " + dest; + + //System.out.println(COMMAND); + boolean done = new ExternalProcessExecutor().exec(COMMAND, 60000); if (!done) { success = false; @@ -110,10 +112,7 @@ private boolean generateTextFiles(File textfilesDir, String logStr = gson.toJson(logData); log.warn(" --analytics-- data={}", logStr); - break; } - } - } return success; diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/ThumbnailCreatorImp.java b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/ThumbnailCreatorImp.java index f4c05c8c9256..59f04bb89441 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/ThumbnailCreatorImp.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/ThumbnailCreatorImp.java @@ -36,11 +36,9 @@ import com.google.gson.Gson; public class ThumbnailCreatorImp implements ThumbnailCreator { - private static Logger log = LoggerFactory - .getLogger(ThumbnailCreatorImp.class); + private static Logger log = LoggerFactory.getLogger(ThumbnailCreatorImp.class); - private static final Pattern PAGE_NUMBER_PATTERN = Pattern - .compile("(.+-thumb)-([0-9]+)(.png)"); + private static final Pattern PAGE_NUMBER_PATTERN = Pattern.compile("(.+-thumb)-([0-9]+)-([0-9]+)(.png)"); private static String TEMP_THUMB_NAME = "temp-thumb"; @@ -49,44 +47,46 @@ public class ThumbnailCreatorImp implements ThumbnailCreator { private String BLANK_THUMBNAIL; @Override - public boolean createThumbnails(UploadedPresentation pres) { + public boolean createThumbnail(UploadedPresentation pres, int page, File pageFile) { boolean success = false; File thumbsDir = determineThumbnailDirectory(pres.getUploadedFile()); if (!thumbsDir.exists()) thumbsDir.mkdir(); - cleanDirectory(thumbsDir); - try { - success = generateThumbnails(thumbsDir, pres); + success = generateThumbnail(thumbsDir, pres, page, pageFile); } catch (InterruptedException e) { log.error("Interrupted Exception while generating thumbnails {}", pres.getName(), e); success = false; } + renameThumbnails(thumbsDir, page); + // Create blank thumbnails for pages that failed to generate a thumbnail. - createBlankThumbnails(thumbsDir, pres.getNumberOfPages()); + createBlankThumbnail(thumbsDir, page); - renameThumbnails(thumbsDir); return success; } - private boolean generateThumbnails(File thumbsDir, UploadedPresentation pres) + private boolean generateThumbnail(File thumbsDir, UploadedPresentation pres, int page, File pageFile) throws InterruptedException { - String source = pres.getUploadedFile().getAbsolutePath(); + String source = pageFile.getAbsolutePath(); String dest; String COMMAND = ""; - dest = thumbsDir.getAbsolutePath() + File.separatorChar + TEMP_THUMB_NAME; + if (SupportedFileTypes.isImageFile(pres.getFileType())) { - COMMAND = IMAGEMAGICK_DIR + File.separatorChar + "convert -thumbnail 150x150 " - + source + " " + dest + ".png"; + dest = thumbsDir.getAbsolutePath() + File.separatorChar + "thumb-" + page + ".png"; + COMMAND = IMAGEMAGICK_DIR + File.separatorChar + "convert -thumbnail 150x150 " + source + " " + dest; } else { + dest = thumbsDir.getAbsolutePath() + File.separatorChar + TEMP_THUMB_NAME + "-" + page; // the "-x.png" is appended automagically COMMAND = "pdftocairo -png -scale-to 150 " + source + " " + dest; } - boolean done = new ExternalProcessExecutor().exec(COMMAND, 60000); + //System.out.println(COMMAND); + + boolean done = new ExternalProcessExecutor().exec(COMMAND, 10000); if (done) { return true; @@ -95,6 +95,7 @@ private boolean generateThumbnails(File thumbsDir, UploadedPresentation pres) logData.put("meetingId", pres.getMeetingId()); logData.put("presId", pres.getId()); logData.put("filename", pres.getName()); + logData.put("page", page); logData.put("logCode", "create_thumbnails_failed"); logData.put("message", "Failed to create thumbnails."); @@ -111,7 +112,7 @@ private File determineThumbnailDirectory(File presentationFile) { presentationFile.getParent() + File.separatorChar + "thumbnails"); } - private void renameThumbnails(File dir) { + private void renameThumbnails(File dir, int page) { /* * If more than 1 file, filename like 'temp-thumb-X.png' else filename is * 'temp-thumb.png' @@ -131,10 +132,12 @@ private void renameThumbnails(File dir) { // 3. .png // We are interested in the second match. int pageNum = Integer.valueOf(matcher.group(2).trim()).intValue(); - String newFilename = "thumb-" + (pageNum) + ".png"; - File renamedFile = new File( - dir.getAbsolutePath() + File.separatorChar + newFilename); - file.renameTo(renamedFile); + if (pageNum == page) { + String newFilename = "thumb-" + (page) + ".png"; + File renamedFile = new File( + dir.getAbsolutePath() + File.separatorChar + newFilename); + file.renameTo(renamedFile); + } } } } else if (dir.list().length == 1) { @@ -147,18 +150,13 @@ private void renameThumbnails(File dir) { } } - private void createBlankThumbnails(File thumbsDir, int pageCount) { + private void createBlankThumbnail(File thumbsDir, int page) { File[] thumbs = thumbsDir.listFiles(); - if (thumbs.length != pageCount) { - for (int i = 0; i < pageCount; i++) { - File thumb = new File(thumbsDir.getAbsolutePath() + File.separatorChar - + TEMP_THUMB_NAME + "-" + i + ".png"); - if (!thumb.exists()) { - log.info("Copying blank thumbnail for slide {}", i); - copyBlankThumbnail(thumb); - } - } + File thumb = new File(thumbsDir.getAbsolutePath() + File.separatorChar + "thumb-" + page + ".png"); + if (!thumb.exists()) { + log.info("Copying blank thumbnail for slide {}", page); + copyBlankThumbnail(thumb); } } diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/messages/DocConversionRequestReceived.java b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/messages/DocConversionRequestReceived.java new file mode 100755 index 000000000000..ed3ced6ce134 --- /dev/null +++ b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/messages/DocConversionRequestReceived.java @@ -0,0 +1,27 @@ +package org.bigbluebutton.presentation.messages; + +public class DocConversionRequestReceived implements IDocConversionMsg { + public final String podId; + public final String meetingId; + public final String presId; + public final String filename; + public final String authzToken; + public final Boolean downloadable; + public final Boolean current; + + public DocConversionRequestReceived(String podId, + String meetingId, + String presId, + String filename, + String authzToken, + Boolean downloadable, + Boolean current) { + this.podId = podId; + this.meetingId = meetingId; + this.presId = presId; + this.filename = filename; + this.authzToken = authzToken; + this.downloadable = downloadable; + this.current = current; + } +} diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/messages/DocPageConversionStarted.java b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/messages/DocPageConversionStarted.java new file mode 100755 index 000000000000..45db2929d60c --- /dev/null +++ b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/messages/DocPageConversionStarted.java @@ -0,0 +1,30 @@ +package org.bigbluebutton.presentation.messages; + +public class DocPageConversionStarted implements IDocConversionMsg { + public final String podId; + public final String meetingId; + public final String presId; + public final String filename; + public final String authzToken; + public final Boolean downloadable; + public final Boolean current; + public final Integer numPages; + + public DocPageConversionStarted(String podId, + String meetingId, + String presId, + String filename, + String authzToken, + Boolean downloadable, + Boolean current, + Integer numPages) { + this.podId = podId; + this.meetingId = meetingId; + this.presId = presId; + this.filename = filename; + this.authzToken = authzToken; + this.downloadable = downloadable; + this.current = current; + this.numPages = numPages; + } +} diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/messages/DocPageGeneratedProgress.java b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/messages/DocPageGeneratedProgress.java index a67249da85c8..8a8fbe4119f3 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/messages/DocPageGeneratedProgress.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/messages/DocPageGeneratedProgress.java @@ -12,11 +12,24 @@ public class DocPageGeneratedProgress implements IDocConversionMsg { public final String key; public final Integer numPages; public final Integer pagesCompleted; + public final String presBaseUrl; + public final Boolean current; + public final Integer page; - public DocPageGeneratedProgress(String podId, String meetingId, String presId, String presInstance, - String filename, String uploaderId, String authzToken, - Boolean downloadable, String key, - Integer numPages, Integer pagesCompleted) { + public DocPageGeneratedProgress(String podId, + String meetingId, + String presId, + String presInstance, + String filename, + String uploaderId, + String authzToken, + Boolean downloadable, + String key, + Integer numPages, + Integer pagesCompleted, + String presBaseUrl, + Integer page, + Boolean current) { this.podId = podId; this.meetingId = meetingId; this.presId = presId; @@ -28,5 +41,8 @@ public DocPageGeneratedProgress(String podId, String meetingId, String presId, S this.key = key; this.numPages = numPages; this.pagesCompleted = pagesCompleted; + this.presBaseUrl = presBaseUrl; + this.page = page; + this.current = current; } } diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/messages/IPresentationCompletionMessage.java b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/messages/IPresentationCompletionMessage.java new file mode 100755 index 000000000000..329d1f3e7d63 --- /dev/null +++ b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/messages/IPresentationCompletionMessage.java @@ -0,0 +1,4 @@ +package org.bigbluebutton.presentation.messages; + +public interface IPresentationCompletionMessage { +} diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/messages/PageConvertProgressMessage.java b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/messages/PageConvertProgressMessage.java new file mode 100755 index 000000000000..0b235e0447be --- /dev/null +++ b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/messages/PageConvertProgressMessage.java @@ -0,0 +1,16 @@ +package org.bigbluebutton.presentation.messages; + +import java.util.List; + +public class PageConvertProgressMessage implements IPresentationCompletionMessage { + + public final String presId; + public final int page; + public final List errors; + + public PageConvertProgressMessage(int page, String presId, List errors) { + this.presId = presId; + this.page = page; + this.errors = errors; + } +} diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/messages/PresentationConvertMessage.java b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/messages/PresentationConvertMessage.java new file mode 100755 index 000000000000..73b7cdf7f85e --- /dev/null +++ b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/messages/PresentationConvertMessage.java @@ -0,0 +1,11 @@ +package org.bigbluebutton.presentation.messages; + +import org.bigbluebutton.presentation.UploadedPresentation; + +public class PresentationConvertMessage implements IPresentationCompletionMessage { + public final UploadedPresentation pres; + + public PresentationConvertMessage(UploadedPresentation pres) { + this.pres = pres; + } +} diff --git a/bbb-common-web/src/main/scala/org/bigbluebutton/api2/BbbWebApiGWApp.scala b/bbb-common-web/src/main/scala/org/bigbluebutton/api2/BbbWebApiGWApp.scala index 112fe63c7168..423d069ee8d0 100755 --- a/bbb-common-web/src/main/scala/org/bigbluebutton/api2/BbbWebApiGWApp.scala +++ b/bbb-common-web/src/main/scala/org/bigbluebutton/api2/BbbWebApiGWApp.scala @@ -274,12 +274,21 @@ class BbbWebApiGWApp( if (msg.isInstanceOf[DocPageGeneratedProgress]) { val event = MsgBuilder.buildPresentationPageGeneratedPubMsg(msg.asInstanceOf[DocPageGeneratedProgress]) msgToAkkaAppsEventBus.publish(MsgToAkkaApps(toAkkaAppsChannel, event)) + + // Send new event with page urls + val newEvent = MsgBuilder.buildPresentationPageConvertedSysMsg(msg.asInstanceOf[DocPageGeneratedProgress]) + msgToAkkaAppsEventBus.publish(MsgToAkkaApps(toAkkaAppsChannel, newEvent)) } else if (msg.isInstanceOf[OfficeDocConversionProgress]) { val event = MsgBuilder.buildPresentationConversionUpdateSysPubMsg(msg.asInstanceOf[OfficeDocConversionProgress]) msgToAkkaAppsEventBus.publish(MsgToAkkaApps(toAkkaAppsChannel, event)) } else if (msg.isInstanceOf[DocPageCompletedProgress]) { val event = MsgBuilder.buildPresentationConversionCompletedSysPubMsg(msg.asInstanceOf[DocPageCompletedProgress]) msgToAkkaAppsEventBus.publish(MsgToAkkaApps(toAkkaAppsChannel, event)) + + // Send new event with page urls + val newEvent = MsgBuilder.buildPresentationConversionEndedSysMsg(msg.asInstanceOf[DocPageCompletedProgress]) + msgToAkkaAppsEventBus.publish(MsgToAkkaApps(toAkkaAppsChannel, newEvent)) + } else if (msg.isInstanceOf[DocPageCountFailed]) { val event = MsgBuilder.buildPresentationPageCountFailedSysPubMsg(msg.asInstanceOf[DocPageCountFailed]) msgToAkkaAppsEventBus.publish(MsgToAkkaApps(toAkkaAppsChannel, event)) @@ -289,6 +298,12 @@ class BbbWebApiGWApp( } else if (msg.isInstanceOf[PdfConversionInvalid]) { val event = MsgBuilder.buildPdfConversionInvalidErrorSysPubMsg(msg.asInstanceOf[PdfConversionInvalid]) msgToAkkaAppsEventBus.publish(MsgToAkkaApps(toAkkaAppsChannel, event)) + } else if (msg.isInstanceOf[DocConversionRequestReceived]) { + val event = MsgBuilder.buildPresentationConversionRequestReceivedSysMsg(msg.asInstanceOf[DocConversionRequestReceived]) + msgToAkkaAppsEventBus.publish(MsgToAkkaApps(toAkkaAppsChannel, event)) + } else if (msg.isInstanceOf[DocPageConversionStarted]) { + val event = MsgBuilder.buildPresentationPageConversionStartedSysMsg(msg.asInstanceOf[DocPageConversionStarted]) + msgToAkkaAppsEventBus.publish(MsgToAkkaApps(toAkkaAppsChannel, event)) } } diff --git a/bbb-common-web/src/main/scala/org/bigbluebutton/api2/MsgBuilder.scala b/bbb-common-web/src/main/scala/org/bigbluebutton/api2/MsgBuilder.scala index c5ead9b5d930..3511bef67d03 100755 --- a/bbb-common-web/src/main/scala/org/bigbluebutton/api2/MsgBuilder.scala +++ b/bbb-common-web/src/main/scala/org/bigbluebutton/api2/MsgBuilder.scala @@ -2,7 +2,7 @@ package org.bigbluebutton.api2 import org.bigbluebutton.api.messaging.converters.messages._ import org.bigbluebutton.api2.meeting.RegisterUser -import org.bigbluebutton.common2.domain.{ DefaultProps, PageVO, PresentationVO } +import org.bigbluebutton.common2.domain.{ DefaultProps, PageVO, PresentationPageConvertedVO, PresentationVO } import org.bigbluebutton.common2.msgs._ import org.bigbluebutton.presentation.messages._ @@ -64,10 +64,52 @@ object MsgBuilder { BbbCommonEnvCoreMsg(envelope, req) } + def generatePresentationPage(presId: String, numPages: Int, presBaseUrl: String, page: Int): PresentationPageConvertedVO = { + val id = presId + "/" + page + val current = if (page == 1) true else false + val thumbUrl = presBaseUrl + "/thumbnail/" + page + val swfUrl = presBaseUrl + "/slide/" + page + + val txtUrl = presBaseUrl + "/textfiles/" + page + val svgUrl = presBaseUrl + "/svg/" + page + val pngUrl = presBaseUrl + "/png/" + page + + val urls = Map("swf" -> swfUrl, "thumb" -> thumbUrl, "text" -> txtUrl, "svg" -> svgUrl, "png" -> pngUrl) + + PresentationPageConvertedVO( + id = id, + num = page, + urls = urls, + current = current + ) + } + + def buildPresentationPageConvertedSysMsg(msg: DocPageGeneratedProgress): BbbCommonEnvCoreMsg = { + val routing = collection.immutable.HashMap("sender" -> "bbb-web") + val envelope = BbbCoreEnvelope(PresentationPageConvertedSysMsg.NAME, routing) + val header = BbbClientMsgHeader(PresentationPageConvertedSysMsg.NAME, msg.meetingId, msg.authzToken) + + val page = generatePresentationPage(msg.presId, msg.numPages.intValue(), msg.presBaseUrl, msg.page.intValue()) + + val body = PresentationPageConvertedSysMsgBody( + podId = msg.podId, + messageKey = msg.key, + code = msg.key, + presentationId = msg.presId, + numberOfPages = msg.numPages.intValue(), + pagesCompleted = msg.pagesCompleted.intValue(), + presName = msg.filename, + page + ) + val req = PresentationPageConvertedSysMsg(header, body) + BbbCommonEnvCoreMsg(envelope, req) + } + def buildPresentationPageGeneratedPubMsg(msg: DocPageGeneratedProgress): BbbCommonEnvCoreMsg = { val routing = collection.immutable.HashMap("sender" -> "bbb-web") val envelope = BbbCoreEnvelope(PresentationPageGeneratedSysPubMsg.NAME, routing) val header = BbbClientMsgHeader(PresentationPageGeneratedSysPubMsg.NAME, msg.meetingId, msg.authzToken) + val body = PresentationPageGeneratedSysPubMsgBody(podId = msg.podId, messageKey = msg.key, code = msg.key, presentationId = msg.presId, numberOfPages = msg.numPages.intValue(), pagesCompleted = msg.pagesCompleted.intValue(), presName = msg.filename) @@ -85,6 +127,20 @@ object MsgBuilder { BbbCommonEnvCoreMsg(envelope, req) } + def buildPresentationConversionEndedSysMsg(msg: DocPageCompletedProgress): BbbCommonEnvCoreMsg = { + val routing = collection.immutable.HashMap("sender" -> "bbb-web") + val envelope = BbbCoreEnvelope(PresentationConversionEndedSysMsg.NAME, routing) + val header = BbbClientMsgHeader(PresentationConversionEndedSysMsg.NAME, msg.meetingId, msg.authzToken) + + val body = PresentationConversionEndedSysMsgBody( + podId = msg.podId, + presentationId = msg.presId, + presName = msg.filename + ) + val req = PresentationConversionEndedSysMsg(header, body) + BbbCommonEnvCoreMsg(envelope, req) + } + def buildPresentationConversionCompletedSysPubMsg(msg: DocPageCompletedProgress): BbbCommonEnvCoreMsg = { val routing = collection.immutable.HashMap("sender" -> "bbb-web") val envelope = BbbCoreEnvelope(PresentationConversionCompletedSysPubMsg.NAME, routing) @@ -143,7 +199,7 @@ object MsgBuilder { BbbCommonEnvCoreMsg(envelope, req) } - def buildPdfConversionInvalidErrorSysPubMsg(msg: PdfConversionInvalid): BbbCommonEnvCoreMsg ={ + def buildPdfConversionInvalidErrorSysPubMsg(msg: PdfConversionInvalid): BbbCommonEnvCoreMsg = { val routing = collection.immutable.HashMap("sender" -> "bbb-web") val envelope = BbbCoreEnvelope(PdfConversionInvalidErrorSysPubMsg.NAME, routing) val header = BbbClientMsgHeader(PdfConversionInvalidErrorSysPubMsg.NAME, msg.meetingId, msg.authzToken) @@ -153,7 +209,42 @@ object MsgBuilder { val req = PdfConversionInvalidErrorSysPubMsg(header, body) BbbCommonEnvCoreMsg(envelope, req) } - + + def buildPresentationConversionRequestReceivedSysMsg(msg: DocConversionRequestReceived): BbbCommonEnvCoreMsg = { + val routing = collection.immutable.HashMap("sender" -> "bbb-web") + val envelope = BbbCoreEnvelope(PresentationConversionRequestReceivedSysMsg.NAME, routing) + val header = BbbClientMsgHeader(PresentationConversionRequestReceivedSysMsg.NAME, msg.meetingId, msg.authzToken) + + val body = PresentationConversionRequestReceivedSysMsgBody( + podId = msg.podId, + presentationId = msg.presId, + current = msg.current, + presName = msg.filename, + downloadable = msg.downloadable, + authzToken = msg.authzToken + ) + val req = PresentationConversionRequestReceivedSysMsg(header, body) + BbbCommonEnvCoreMsg(envelope, req) + } + + def buildPresentationPageConversionStartedSysMsg(msg: DocPageConversionStarted): BbbCommonEnvCoreMsg = { + val routing = collection.immutable.HashMap("sender" -> "bbb-web") + val envelope = BbbCoreEnvelope(PresentationPageConversionStartedSysMsg.NAME, routing) + val header = BbbClientMsgHeader(PresentationPageConversionStartedSysMsg.NAME, msg.meetingId, msg.authzToken) + + val body = PresentationPageConversionStartedSysMsgBody( + podId = msg.podId, + presentationId = msg.presId, + current = msg.current, + presName = msg.filename, + downloadable = msg.downloadable, + numPages = msg.numPages, + authzToken = msg.authzToken + ) + val req = PresentationPageConversionStartedSysMsg(header, body) + BbbCommonEnvCoreMsg(envelope, req) + } + def buildPublishedRecordingSysMsg(msg: PublishedRecordingMessage): BbbCommonEnvCoreMsg = { val routing = collection.immutable.HashMap("sender" -> "bbb-web") val envelope = BbbCoreEnvelope(PublishedRecordingSysMsg.NAME, routing) diff --git a/bigbluebutton-config/slides/blank-png.png b/bigbluebutton-config/slides/blank-png.png new file mode 100644 index 000000000000..39a230dccb01 Binary files /dev/null and b/bigbluebutton-config/slides/blank-png.png differ diff --git a/bigbluebutton-config/slides/blank-presentation.pdf b/bigbluebutton-config/slides/blank-presentation.pdf index ebf6257403b8..02e8b9cc06ca 100644 Binary files a/bigbluebutton-config/slides/blank-presentation.pdf and b/bigbluebutton-config/slides/blank-presentation.pdf differ diff --git a/bigbluebutton-config/slides/blank-svg.svg b/bigbluebutton-config/slides/blank-svg.svg new file mode 100644 index 000000000000..9c1a95298480 --- /dev/null +++ b/bigbluebutton-config/slides/blank-svg.svg @@ -0,0 +1,4 @@ + + + diff --git a/bigbluebutton-web/grails-app/conf/bigbluebutton.properties b/bigbluebutton-web/grails-app/conf/bigbluebutton.properties index 6bd5cacf22ab..c6587256c1da 100755 --- a/bigbluebutton-web/grails-app/conf/bigbluebutton.properties +++ b/bigbluebutton-web/grails-app/conf/bigbluebutton.properties @@ -43,6 +43,10 @@ fontsDir=/usr/share/fonts # Executable for presentation checker presCheckExec=/usr/share/prescheck/prescheck.sh +#---------------------------------------------------- +# Skip Office doc conversion pre-check. Attempt to convert +# Office doc to PDF right away. +skipOfficePrecheck=true #---------------------------------------------------- # These will be copied in cases where the conversion process @@ -51,6 +55,7 @@ BLANK_SLIDE=/var/bigbluebutton/blank/blank-slide.swf BLANK_PRESENTATION=/var/bigbluebutton/blank/blank-presentation.pdf BLANK_THUMBNAIL=/var/bigbluebutton/blank/blank-thumb.png BLANK_PNG=/var/bigbluebutton/blank/blank-png.png +BLANK_SVG=/var/bigbluebutton/blank/blank-svg.svg #---------------------------------------------------- # Number of minutes the conversion should take. If it takes @@ -82,7 +87,12 @@ defineTextThreshold=2000 #------------------------------------ # Number of threads in the pool to do the presentation conversion. #------------------------------------ -numConversionThreads=2 +numConversionThreads=5 + +#------------------------------------ +# Number of threads to process file uploads +#------------------------------------ +numFileProcessorThreads=2 #---------------------------------------------------- # Conversion of the presentation slides to SWF to be @@ -111,8 +121,8 @@ maxImageSize=2000000 # Configuration for large PDF, 14 MB by default, if bigger it will be analysed during the conversion process bigPdfSize=14000000 -# The maximum allowed page size for PDF files exceeding the 'pdfCheckSize' value, 12 MB by default -maxBigPdfPageSize=12000000 +# The maximum allowed page size for PDF files exceeding the 'pdfCheckSize' value, 2 MB by default +maxBigPdfPageSize=2000000 #---------------------------------------------------- # Default dial access number @@ -193,7 +203,7 @@ autoStartRecording=false allowStartStopRecording=true # Allow webcams streaming reception only to and from moderators -webcamsOnlyForModerator=false +webcamsOnlyForModerator=false # Mute the meeting on start muteOnStart=false diff --git a/bigbluebutton-web/grails-app/conf/spring/doc-conversion.xml b/bigbluebutton-web/grails-app/conf/spring/doc-conversion.xml index 96b7bd1d77be..6658ebc5ef39 100755 --- a/bigbluebutton-web/grails-app/conf/spring/doc-conversion.xml +++ b/bigbluebutton-web/grails-app/conf/spring/doc-conversion.xml @@ -26,8 +26,8 @@ with BigBlueButton; if not, see . - - + + @@ -38,6 +38,7 @@ with BigBlueButton; if not, see . + @@ -83,6 +84,7 @@ with BigBlueButton; if not, see . + @@ -90,7 +92,12 @@ with BigBlueButton; if not, see . - + + + + + @@ -106,6 +113,10 @@ with BigBlueButton; if not, see . + + + + . + + + + + diff --git a/bigbluebutton-web/grails-app/conf/spring/resources.xml b/bigbluebutton-web/grails-app/conf/spring/resources.xml index 505612447abe..df4684abf728 100755 --- a/bigbluebutton-web/grails-app/conf/spring/resources.xml +++ b/bigbluebutton-web/grails-app/conf/spring/resources.xml @@ -27,6 +27,12 @@ with BigBlueButton; if not, see . http://www.springframework.org/schema/util/spring-util-2.0.xsd"> + + + utf-8 + + +