Merge remote-tracking branch 'upstream/master' into merge-webrtc-screenshare-2
This commit is contained in:
commit
4d8af68fc5
@ -28,6 +28,10 @@ public class ParticipantJoinRecordEvent extends AbstractParticipantRecordEvent {
|
||||
public void setUserId(String userId) {
|
||||
eventMap.put("userId", userId);
|
||||
}
|
||||
|
||||
public void setExternalUserId(String externalUserId) {
|
||||
eventMap.put("externalUserId", externalUserId);
|
||||
}
|
||||
|
||||
public void setName(String name){
|
||||
eventMap.put("name",name);
|
||||
|
@ -43,7 +43,6 @@ http {
|
||||
services {
|
||||
bbbWebAPI = "http://192.168.23.33/bigbluebutton/api"
|
||||
sharedSecret = "changeme"
|
||||
defaultPresentationURL = "http://localhost/default.pdf"
|
||||
}
|
||||
|
||||
red5 {
|
||||
|
@ -18,7 +18,6 @@ trait SystemConfiguration {
|
||||
lazy val bbbWebSharedSecret = Try(config.getString("services.sharedSecret")).getOrElse("changeme")
|
||||
lazy val bbbWebModeratorPassword = Try(config.getString("services.moderatorPassword")).getOrElse("changeme")
|
||||
lazy val bbbWebViewerPassword = Try(config.getString("services.viewerPassword")).getOrElse("changeme")
|
||||
lazy val bbbWebDefaultPresentationURL = Try(config.getString("services.defaultPresentationURL")).getOrElse("changeme")
|
||||
lazy val keysExpiresInSec = Try(config.getInt("redis.keyExpiry")).getOrElse(14 * 86400) // 14 days
|
||||
lazy val red5DeskShareIP = Try(config.getString("red5.deskshareip")).getOrElse("127.0.0.1")
|
||||
lazy val red5DeskShareApp = Try(config.getString("red5.deskshareapp")).getOrElse("")
|
||||
|
@ -94,7 +94,7 @@ class JsonMessageSenderActor(val service: MessageSender)
|
||||
private def handleCreateBreakoutRoom(msg: CreateBreakoutRoom) {
|
||||
val payload = new CreateBreakoutRoomRequestPayload(msg.room.breakoutId, msg.room.parentId, msg.room.name,
|
||||
msg.room.voiceConfId, msg.room.viewerPassword, msg.room.moderatorPassword,
|
||||
msg.room.durationInMinutes, msg.room.defaultPresentationURL, msg.room.record)
|
||||
msg.room.durationInMinutes, msg.room.sourcePresentationId, msg.room.sourcePresentationSlide, msg.room.record)
|
||||
val request = new CreateBreakoutRoomRequest(payload)
|
||||
service.send(MessagingConstants.FROM_MEETING_CHANNEL, request.toJson())
|
||||
}
|
||||
|
@ -117,7 +117,7 @@ class LiveMeeting(val mProps: MeetingProperties,
|
||||
meetingModel.meetingHasEnded
|
||||
|
||||
/**
|
||||
* See if this meeting has breakout rooms. If so, we also need to end them.
|
||||
* Check if this meeting has breakout rooms. If so, we also need to end them.
|
||||
*/
|
||||
handleEndAllBreakoutRooms(new EndAllBreakoutRooms(msg.meetingId))
|
||||
|
||||
|
@ -33,7 +33,7 @@ object UserMessagesProtocol extends DefaultJsonProtocol {
|
||||
implicit val inMsgHeaderFormat = jsonFormat1(InMessageHeader)
|
||||
implicit val outMsgHeaderFormat = jsonFormat1(OutMsgHeader)
|
||||
implicit val outMsgEnvelopeHeaderFormat = jsonFormat2(OutMsgEnvelopeHeader)
|
||||
implicit val createBreakoutRoomOutMsgPayloadFormat = jsonFormat8(CreateBreakoutRoomOutMsgPayload)
|
||||
implicit val createBreakoutRoomOutMsgPayloadFormat = jsonFormat10(CreateBreakoutRoomOutMsgPayload)
|
||||
implicit val createBreakoutRoomOutMsgEnvelopePayloadFormat = jsonFormat2(CreateBreakoutRoomOutMsgEnvelopePayload)
|
||||
implicit val createBreakoutRoomOutMsgEnvelopeFormat = jsonFormat2(CreateBreakoutRoomOutMsgEnvelope)
|
||||
|
||||
|
@ -5,12 +5,10 @@ case class OutMsgEnvelopeHeader(`type`: MessageType.MessageType, address: String
|
||||
|
||||
trait OutMessage
|
||||
|
||||
case class CreateBreakoutRoomOutMsgEnvelope(header: OutMsgEnvelopeHeader,
|
||||
payload: CreateBreakoutRoomOutMsgEnvelopePayload)
|
||||
case class CreateBreakoutRoomOutMsgEnvelopePayload(header: OutMsgHeader,
|
||||
payload: CreateBreakoutRoomOutMsgPayload)
|
||||
case class CreateBreakoutRoomOutMsgEnvelope(header: OutMsgEnvelopeHeader, payload: CreateBreakoutRoomOutMsgEnvelopePayload)
|
||||
case class CreateBreakoutRoomOutMsgEnvelopePayload(header: OutMsgHeader, payload: CreateBreakoutRoomOutMsgPayload)
|
||||
case class CreateBreakoutRoomOutMsgPayload(breakoutId: String, name: String, parentId: String,
|
||||
voiceConfId: String, durationInMinutes: Int,
|
||||
moderatorPassword: String, viewerPassword: String,
|
||||
defaultPresentationUrl: String)
|
||||
sourcePresentationId: String, sourcePresentationSlide: Int, record: Boolean)
|
||||
|
||||
|
@ -196,6 +196,7 @@ class RecorderActor(val recorder: RecorderApplication)
|
||||
val ev = new ParticipantJoinRecordEvent();
|
||||
ev.setTimestamp(TimestampGenerator.generateTimestamp);
|
||||
ev.setUserId(msg.user.userID);
|
||||
ev.setExternalUserId(msg.user.externUserID);
|
||||
ev.setName(msg.user.name);
|
||||
ev.setMeetingId(msg.meetingID);
|
||||
ev.setRole(msg.user.role.toString());
|
||||
|
@ -33,7 +33,7 @@ case class CreateBreakoutRoom(meetingId: String, room: BreakoutRoomOutPayload) e
|
||||
case class EndBreakoutRoom(breakoutId: String) extends IOutMessage
|
||||
case class BreakoutRoomOutPayload(breakoutId: String, name: String, parentId: String,
|
||||
voiceConfId: String, durationInMinutes: Int, moderatorPassword: String, viewerPassword: String,
|
||||
defaultPresentationURL: String, record: Boolean)
|
||||
sourcePresentationId: String, sourcePresentationSlide: Int, record: Boolean)
|
||||
case class BreakoutRoomJoinURLOutMessage(meetingId: String, recorded: Boolean, breakoutId: String, userId: String, joinURL: String) extends IOutMessage
|
||||
case class BreakoutRoomStartedOutMessage(meetingId: String, recorded: Boolean, breakout: BreakoutRoomBody) extends IOutMessage
|
||||
case class BreakoutRoomBody(name: String, breakoutId: String)
|
||||
|
@ -20,15 +20,6 @@ trait BreakoutRoomApp extends SystemConfiguration {
|
||||
val outGW: OutMessageGateway
|
||||
val eventBus: IncomingEventBus
|
||||
|
||||
def getDefaultPresentationURL(): String = {
|
||||
var presURL = bbbWebDefaultPresentationURL
|
||||
val page = presModel.getCurrentPage()
|
||||
page foreach { p =>
|
||||
presURL = BreakoutRoomsUtil.fromSWFtoPDF(p.swfUri)
|
||||
}
|
||||
presURL
|
||||
}
|
||||
|
||||
def handleBreakoutRoomsList(msg: BreakoutRoomsListMessage) {
|
||||
val breakoutRooms = breakoutModel.getRooms().toVector map { r => new BreakoutRoomBody(r.name, r.id) }
|
||||
outGW.send(new BreakoutRoomsListOutMessage(mProps.meetingID, breakoutRooms, breakoutModel.pendingRoomsNumber == 0 && breakoutRooms.length > 0));
|
||||
@ -40,17 +31,20 @@ trait BreakoutRoomApp extends SystemConfiguration {
|
||||
log.warning("CreateBreakoutRooms event received while {} are pending to be created for meeting {}", breakoutModel.pendingRoomsNumber, mProps.meetingID)
|
||||
return
|
||||
}
|
||||
|
||||
var i = 0
|
||||
val sourcePresentationId = presModel.getCurrentPresentation().get.id
|
||||
val sourcePresentationSlide = presModel.getCurrentPage().get.num
|
||||
breakoutModel.pendingRoomsNumber = msg.rooms.length;
|
||||
|
||||
for (room <- msg.rooms) {
|
||||
i += 1
|
||||
val presURL = bbbWebDefaultPresentationURL
|
||||
val breakoutMeetingId = BreakoutRoomsUtil.createMeetingId(mProps.meetingID, i)
|
||||
val voiceConfId = BreakoutRoomsUtil.createVoiceConfId(mProps.voiceBridge, i)
|
||||
val r = breakoutModel.createBreakoutRoom(breakoutMeetingId, room.name, voiceConfId, room.users, presURL)
|
||||
val r = breakoutModel.createBreakoutRoom(breakoutMeetingId, room.name, voiceConfId, room.users)
|
||||
val p = new BreakoutRoomOutPayload(r.id, r.name, mProps.meetingID,
|
||||
r.voiceConfId, msg.durationInMinutes, mProps.moderatorPass, mProps.viewerPass,
|
||||
r.defaultPresentationURL, msg.record)
|
||||
sourcePresentationId, sourcePresentationSlide, msg.record)
|
||||
outGW.send(new CreateBreakoutRoom(mProps.meetingID, p))
|
||||
}
|
||||
meetingModel.breakoutRoomsdurationInMinutes = msg.durationInMinutes;
|
||||
@ -85,7 +79,7 @@ trait BreakoutRoomApp extends SystemConfiguration {
|
||||
breakoutModel.getRooms().foreach { room =>
|
||||
breakoutModel.getAssignedUsers(room.id) foreach { users =>
|
||||
users.foreach { u =>
|
||||
log.debug("## Sending Join URL for users: {}", u);
|
||||
log.debug("Sending Join URL for users: {}", u);
|
||||
sendJoinURL(u, room.id)
|
||||
}
|
||||
}
|
||||
@ -181,7 +175,7 @@ object BreakoutRoomsUtil {
|
||||
}
|
||||
|
||||
def joinParams(username: String, userId: String, isBreakout: Boolean, breakoutId: String,
|
||||
password: String, redirect: Boolean): mutable.Map[String, String] = {
|
||||
password: String, redirect: Boolean): mutable.Map[String, String] = {
|
||||
val params = new collection.mutable.HashMap[String, String]
|
||||
params += "fullName" -> urlEncode(username)
|
||||
params += "userID" -> urlEncode(userId + "-" + breakoutId.substring(breakoutId.lastIndexOf("-") + 1));
|
||||
|
@ -5,7 +5,7 @@ import scala.collection.immutable.HashMap
|
||||
|
||||
case class BreakoutUser(id: String, name: String)
|
||||
case class BreakoutRoom(id: String, name: String, voiceConfId: String,
|
||||
assignedUsers: Vector[String], users: Vector[BreakoutUser], defaultPresentationURL: String)
|
||||
assignedUsers: Vector[String], users: Vector[BreakoutUser])
|
||||
|
||||
class BreakoutRoomModel {
|
||||
private var rooms = new collection.immutable.HashMap[String, BreakoutRoom]
|
||||
@ -22,8 +22,8 @@ class BreakoutRoomModel {
|
||||
}
|
||||
|
||||
def createBreakoutRoom(id: String, name: String, voiceConfId: String,
|
||||
assignedUsers: Vector[String], defaultPresentationURL: String): BreakoutRoom = {
|
||||
val room = new BreakoutRoom(id, name, voiceConfId, assignedUsers, Vector(), defaultPresentationURL)
|
||||
assignedUsers: Vector[String]): BreakoutRoom = {
|
||||
val room = new BreakoutRoom(id, name, voiceConfId, assignedUsers, Vector())
|
||||
add(room)
|
||||
}
|
||||
|
||||
|
@ -94,16 +94,10 @@ trait PresentationApp {
|
||||
}
|
||||
|
||||
def handleGotoSlide(msg: GotoSlide) {
|
||||
// println("Received GotoSlide for meeting=[" + msg.meetingID + "] page=[" + msg.page + "]")
|
||||
// println("*** Before change page ****")
|
||||
// printPresentations
|
||||
presModel.changePage(msg.page) foreach { page =>
|
||||
// println("Switching page for meeting=[" + msg.meetingID + "] page=[" + page.id + "]")
|
||||
log.debug("Switching page for meeting=[{}] page=[{}]", msg.meetingID, page.num);
|
||||
outGW.send(new GotoSlideOutMsg(mProps.meetingID, mProps.recorded, page))
|
||||
|
||||
}
|
||||
// println("*** After change page ****")
|
||||
// printPresentations
|
||||
|
||||
usersModel.getCurrentPresenter() foreach { pres =>
|
||||
handleStopPollRequest(StopPollRequest(mProps.meetingID, pres.userID))
|
||||
|
@ -29,7 +29,7 @@ import org.bigbluebutton.freeswitch.voice.freeswitch.actions.EjectUserCommand;
|
||||
import org.bigbluebutton.freeswitch.voice.freeswitch.actions.GetAllUsersCommand;
|
||||
import org.bigbluebutton.freeswitch.voice.freeswitch.actions.MuteUserCommand;
|
||||
import org.bigbluebutton.freeswitch.voice.freeswitch.actions.RecordConferenceCommand;
|
||||
import org.bigbluebutton.freeswitch.voice.freeswitch.actions.TransferUsetToMeetingCommand;
|
||||
import org.bigbluebutton.freeswitch.voice.freeswitch.actions.TransferUserToMeetingCommand;
|
||||
import org.bigbluebutton.freeswitch.voice.freeswitch.actions.*;
|
||||
import org.freeswitch.esl.client.inbound.Client;
|
||||
import org.freeswitch.esl.client.inbound.InboundConnectionFailure;
|
||||
@ -128,7 +128,7 @@ public class ConnectionManager {
|
||||
}
|
||||
}
|
||||
|
||||
public void tranfer(TransferUsetToMeetingCommand tutmc) {
|
||||
public void tranfer(TransferUserToMeetingCommand tutmc) {
|
||||
Client c = manager.getESLClient();
|
||||
if (c.canSend()) {
|
||||
c.sendAsyncApiCommand(tutmc.getCommand(), tutmc.getCommandArgs());
|
||||
|
@ -31,7 +31,7 @@ import org.bigbluebutton.freeswitch.voice.freeswitch.actions.FreeswitchCommand;
|
||||
import org.bigbluebutton.freeswitch.voice.freeswitch.actions.GetAllUsersCommand;
|
||||
import org.bigbluebutton.freeswitch.voice.freeswitch.actions.MuteUserCommand;
|
||||
import org.bigbluebutton.freeswitch.voice.freeswitch.actions.RecordConferenceCommand;
|
||||
import org.bigbluebutton.freeswitch.voice.freeswitch.actions.TransferUsetToMeetingCommand;
|
||||
import org.bigbluebutton.freeswitch.voice.freeswitch.actions.TransferUserToMeetingCommand;
|
||||
import org.bigbluebutton.freeswitch.voice.freeswitch.actions.*;
|
||||
|
||||
public class FreeswitchApplication {
|
||||
@ -48,9 +48,12 @@ public class FreeswitchApplication {
|
||||
private final String USER = "0"; /* not used for now */
|
||||
|
||||
private volatile boolean sendMessages = false;
|
||||
|
||||
private final String audioProfile;
|
||||
|
||||
public FreeswitchApplication(ConnectionManager manager) {
|
||||
public FreeswitchApplication(ConnectionManager manager, String profile) {
|
||||
this.manager = manager;
|
||||
this.audioProfile = profile;
|
||||
}
|
||||
|
||||
private void queueMessage(FreeswitchCommand command) {
|
||||
@ -62,12 +65,13 @@ public class FreeswitchApplication {
|
||||
}
|
||||
}
|
||||
|
||||
public void transferUserToMeeting(String voiceConfId,
|
||||
String targetVoiceConfId, String voiceUserId) {
|
||||
TransferUsetToMeetingCommand tutmc = new TransferUsetToMeetingCommand(
|
||||
voiceConfId, targetVoiceConfId, voiceUserId, USER);
|
||||
queueMessage(tutmc);
|
||||
}
|
||||
public void transferUserToMeeting(String voiceConfId,
|
||||
String targetVoiceConfId, String voiceUserId) {
|
||||
TransferUserToMeetingCommand tutmc = new TransferUserToMeetingCommand(
|
||||
voiceConfId, targetVoiceConfId, voiceUserId, this.audioProfile,
|
||||
USER);
|
||||
queueMessage(tutmc);
|
||||
}
|
||||
|
||||
public void start() {
|
||||
sendMessages = true;
|
||||
@ -153,8 +157,8 @@ public class FreeswitchApplication {
|
||||
EjectAllUsersCommand cmd = (EjectAllUsersCommand) command;
|
||||
System.out.println("Sending EjectAllUsersCommand for conference = [" + cmd.getRoom() + "]");
|
||||
manager.ejectAll(cmd);
|
||||
} else if (command instanceof TransferUsetToMeetingCommand) {
|
||||
TransferUsetToMeetingCommand cmd = (TransferUsetToMeetingCommand) command;
|
||||
} else if (command instanceof TransferUserToMeetingCommand) {
|
||||
TransferUserToMeetingCommand cmd = (TransferUserToMeetingCommand) command;
|
||||
System.out.println("Sending TransferUsetToMeetingCommand for conference = ["
|
||||
+ cmd.getRoom() + "]");
|
||||
manager.tranfer(cmd);
|
||||
|
@ -19,21 +19,23 @@
|
||||
|
||||
package org.bigbluebutton.freeswitch.voice.freeswitch.actions;
|
||||
|
||||
public class TransferUsetToMeetingCommand extends FreeswitchCommand {
|
||||
public class TransferUserToMeetingCommand extends FreeswitchCommand {
|
||||
|
||||
private final String targetRoom;
|
||||
private final String participant;
|
||||
private final String targetRoom;
|
||||
private final String participant;
|
||||
private final String audioProfile;
|
||||
|
||||
public TransferUsetToMeetingCommand(String room, String targetRoom,
|
||||
String participant, String requesterId) {
|
||||
super(room, requesterId);
|
||||
this.targetRoom = targetRoom;
|
||||
this.participant = participant;
|
||||
}
|
||||
public TransferUserToMeetingCommand(String room, String targetRoom,
|
||||
String participant, String profile, String requesterId) {
|
||||
super(room, requesterId);
|
||||
this.targetRoom = targetRoom;
|
||||
this.participant = participant;
|
||||
this.audioProfile = profile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCommandArgs() {
|
||||
return room + SPACE + "transfer" + SPACE + targetRoom + SPACE
|
||||
+ participant;
|
||||
}
|
||||
@Override
|
||||
public String getCommandArgs() {
|
||||
return room + SPACE + "transfer" + SPACE + targetRoom + "@"
|
||||
+ this.audioProfile + SPACE + participant;
|
||||
}
|
||||
}
|
@ -24,6 +24,9 @@ freeswitch {
|
||||
port=8021
|
||||
password="ClueCon"
|
||||
}
|
||||
conf {
|
||||
profile="cdquality"
|
||||
}
|
||||
}
|
||||
|
||||
redis {
|
||||
|
@ -31,7 +31,7 @@ object Boot extends App with SystemConfiguration {
|
||||
|
||||
connManager.start()
|
||||
|
||||
val fsApplication = new FreeswitchApplication(connManager)
|
||||
val fsApplication = new FreeswitchApplication(connManager, fsProfile)
|
||||
fsApplication.start()
|
||||
|
||||
val redisMsgReceiver = new RedisMessageReceiver(fsApplication)
|
||||
|
@ -10,6 +10,7 @@ trait SystemConfiguration {
|
||||
lazy val eslHost = Try(config.getString("freeswitch.esl.host")).getOrElse("127.0.0.1")
|
||||
lazy val eslPort = Try(config.getInt("freeswitch.esl.port")).getOrElse(8021)
|
||||
lazy val eslPassword = Try(config.getString("freeswitch.esl.password")).getOrElse("ClueCon")
|
||||
lazy val fsProfile = Try(config.getString("freeswitch.conf.profile")).getOrElse("cdquality")
|
||||
|
||||
lazy val redisHost = Try(config.getString("redis.host")).getOrElse("127.0.0.1")
|
||||
lazy val redisPort = Try(config.getInt("redis.port")).getOrElse(6379)
|
||||
|
@ -1,27 +1,31 @@
|
||||
package org.bigbluebutton.messages.payload;
|
||||
|
||||
public class CreateBreakoutRoomRequestPayload {
|
||||
public final String breakoutId;
|
||||
public final String parentId; // The main meeting internal id
|
||||
public final String name; // The name of the breakout room
|
||||
public final String voiceConfId; // The voice conference id
|
||||
public final String viewerPassword;
|
||||
public final String moderatorPassword;
|
||||
public final Integer durationInMinutes; // The duration of the breakout room
|
||||
public final String defaultPresentationURL;
|
||||
public final Boolean record;
|
||||
|
||||
public CreateBreakoutRoomRequestPayload(String breakoutId, String parentId, String name,
|
||||
String voiceConfId, String viewerPassword, String moderatorPassword,
|
||||
Integer duration, String defaultPresentationURL, Boolean record) {
|
||||
this.breakoutId = breakoutId;
|
||||
this.parentId = parentId;
|
||||
this.name = name;
|
||||
this.voiceConfId = voiceConfId;
|
||||
this.viewerPassword = viewerPassword;
|
||||
this.moderatorPassword = moderatorPassword;
|
||||
this.durationInMinutes = duration;
|
||||
this.defaultPresentationURL = defaultPresentationURL;
|
||||
this.record = record;
|
||||
}
|
||||
public final String breakoutId;
|
||||
public final String parentId; // The main meeting internal id
|
||||
public final String name; // The name of the breakout room
|
||||
public final String voiceConfId; // The voice conference id
|
||||
public final String viewerPassword;
|
||||
public final String moderatorPassword;
|
||||
public final Integer durationInMinutes; // The duration of the breakout room
|
||||
public final String sourcePresentationId;
|
||||
public final Integer sourcePresentationSlide;
|
||||
public final Boolean record;
|
||||
|
||||
public CreateBreakoutRoomRequestPayload(String breakoutId, String parentId,
|
||||
String name, String voiceConfId, String viewerPassword,
|
||||
String moderatorPassword, Integer duration,
|
||||
String sourcePresentationId, Integer sourcePresentationSlide,
|
||||
Boolean record) {
|
||||
this.breakoutId = breakoutId;
|
||||
this.parentId = parentId;
|
||||
this.name = name;
|
||||
this.voiceConfId = voiceConfId;
|
||||
this.viewerPassword = viewerPassword;
|
||||
this.moderatorPassword = moderatorPassword;
|
||||
this.durationInMinutes = duration;
|
||||
this.sourcePresentationId = sourcePresentationId;
|
||||
this.sourcePresentationSlide = sourcePresentationSlide;
|
||||
this.record = record;
|
||||
}
|
||||
}
|
||||
|
@ -9,19 +9,20 @@ import com.google.gson.Gson;
|
||||
public class CreateBreakoutRoomRequestTest {
|
||||
@Test
|
||||
public void testCreateBreakoutRoomRequest() {
|
||||
String breakoutId = "abc123";
|
||||
String breakoutId = "183f0bf3a0982a127bdb8161e0c44eb696b3e75c-1474984695664";
|
||||
String parentId = "abc-123";
|
||||
Integer durationInMinutes = 20;
|
||||
String name = "Breakout room 1";
|
||||
String voiceConfId = "851153";
|
||||
String viewerPassword = "vp";
|
||||
String moderatorPassword = "mp";
|
||||
String defaultPresentationURL = "http://localhost/foo.pdf";
|
||||
String sourcePresentationId = "d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1474984695907";
|
||||
Integer sourePresentationSlide = 5;
|
||||
Boolean record = false;
|
||||
|
||||
CreateBreakoutRoomRequestPayload payload =
|
||||
new CreateBreakoutRoomRequestPayload(breakoutId, parentId, name, voiceConfId,
|
||||
viewerPassword, moderatorPassword, durationInMinutes, defaultPresentationURL, record);
|
||||
viewerPassword, moderatorPassword, durationInMinutes, sourcePresentationId, sourePresentationSlide, record);
|
||||
CreateBreakoutRoomRequest msg = new CreateBreakoutRoomRequest(payload);
|
||||
Gson gson = new Gson();
|
||||
String json = gson.toJson(msg);
|
||||
@ -36,7 +37,8 @@ public class CreateBreakoutRoomRequestTest {
|
||||
Assert.assertEquals(rxMsg.payload.viewerPassword, viewerPassword);
|
||||
Assert.assertEquals(rxMsg.payload.moderatorPassword, moderatorPassword);
|
||||
Assert.assertEquals(rxMsg.payload.durationInMinutes, durationInMinutes);
|
||||
Assert.assertEquals(rxMsg.payload.defaultPresentationURL, defaultPresentationURL);
|
||||
Assert.assertEquals(rxMsg.payload.sourcePresentationId, sourcePresentationId);
|
||||
Assert.assertEquals(rxMsg.payload.sourcePresentationSlide, sourePresentationSlide);
|
||||
Assert.assertEquals(rxMsg.payload.record, record);
|
||||
}
|
||||
}
|
||||
|
@ -1061,6 +1061,10 @@ EmojiGrid {
|
||||
horizontalGap: 6;
|
||||
}
|
||||
|
||||
.breakoutRoomUserWindowHeadingStyle {
|
||||
fontWeight: bold;
|
||||
}
|
||||
|
||||
.breakoutRoomSettingTitleStyle {
|
||||
fontFamily: Arial;
|
||||
fontSize: 20;
|
||||
|
@ -677,11 +677,14 @@ bbb.users.breakout.remainingTimeParent = <b>{1} remaining</b>
|
||||
bbb.users.breakout.calculatingRemainingTime = Calculating remaining time...
|
||||
bbb.users.breakout.remainingTimeEnded = Time ended, breakout room will close.
|
||||
bbb.users.breakout.rooms = Rooms
|
||||
bbb.users.breakout.roomsCombo.accessibilityName = Number of rooms to create
|
||||
bbb.users.breakout.room = Room
|
||||
bbb.users.breakout.randomAssign = Randomly Assign Users
|
||||
bbb.users.breakout.timeLimit = Time Limit
|
||||
bbb.users.breakout.durationStepper.accessibilityName = Time limit in minutes
|
||||
bbb.users.breakout.minutes = Minutes
|
||||
bbb.users.breakout.record = Record
|
||||
bbb.users.breakout.recordCheckbox.accessibilityName = Record breakout rooms
|
||||
bbb.users.breakout.notAssigned = Not Assigned
|
||||
bbb.users.breakout.dragAndDropToolTip = Tip: You can drag and drop users between rooms
|
||||
bbb.users.breakout.start = Start
|
||||
|
@ -94,6 +94,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
messages.push(obj);
|
||||
showNotification();
|
||||
LOGGER.warn("ClientNotification:" + e.title + " " + e.message);
|
||||
}
|
||||
|
||||
private function showNotification():void {
|
||||
|
@ -309,6 +309,8 @@ package org.bigbluebutton.modules.phone.managers
|
||||
});
|
||||
popUpDelayTimer.start();
|
||||
dispatcher.dispatchEvent(new ClientStatusEvent(ClientStatusEvent.FAIL_MESSAGE_EVENT, title, error));
|
||||
|
||||
LOGGER.warn("WebRTCAlert:" + title + " " + error + " " + message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -138,7 +138,8 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
for (var r:int = 0; r < rooms; r++) {
|
||||
var list:BreakoutList = roomsContainer.addChild(new BreakoutList()) as BreakoutList;
|
||||
list.users = new ArrayCollection();
|
||||
list.roomName = ResourceUtil.getInstance().getString('bbb.users.breakout.room') + " " + (r + 1).toString()
|
||||
list.roomName = ResourceUtil.getInstance().getString('bbb.users.breakout.room') + " " + (r + 1).toString();
|
||||
list.usersList.accessibilityName = ResourceUtil.getInstance().getString('bbb.users.breakout.room') + " " + (r + 1).toString();
|
||||
for (var i:int = 0; i < perRoom; i++) {
|
||||
list.users.addItem(users.removeItemAt(Math.floor(Math.random() * users.length)) as BBBUser);
|
||||
}
|
||||
@ -149,6 +150,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
var unassignedList:BreakoutList = roomsContainer.addChild(new BreakoutList()) as BreakoutList;
|
||||
unassignedList.users = new ArrayCollection([me]);
|
||||
unassignedList.roomName = ResourceUtil.getInstance().getString('bbb.users.breakout.notAssigned');
|
||||
unassignedList.usersList.accessibilityName = ResourceUtil.getInstance().getString('bbb.users.breakout.notAssigned');
|
||||
unassignedList.notAssignedUsers = true;
|
||||
startButton.enabled = true;
|
||||
}
|
||||
@ -165,6 +167,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
var list:BreakoutList = roomsContainer.addChild(new BreakoutList()) as BreakoutList;
|
||||
list.users = new ArrayCollection();
|
||||
list.roomName = ResourceUtil.getInstance().getString('bbb.users.breakout.room') + " " + (r).toString();
|
||||
list.usersList.accessibilityName = ResourceUtil.getInstance().getString('bbb.users.breakout.room') + " " + (r).toString();
|
||||
}
|
||||
// Load user assignment from the previous action
|
||||
var unassignedUsers:ArrayCollection = new ArrayCollection();
|
||||
@ -194,6 +197,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
for (var r:int = 0; r < rooms; r++) {
|
||||
var list:BreakoutList = roomsContainer.addChild(new BreakoutList()) as BreakoutList;
|
||||
list.roomName = ResourceUtil.getInstance().getString('bbb.users.breakout.room') + " " + (r + 1).toString()
|
||||
list.usersList.accessibilityName = ResourceUtil.getInstance().getString('bbb.users.breakout.room') + " " + (r + 1).toString()
|
||||
list.users = new ArrayCollection();
|
||||
list.mode = mode;
|
||||
}
|
||||
@ -216,6 +220,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
var unassignedList:BreakoutList = roomsContainer.addChild(new BreakoutList()) as BreakoutList;
|
||||
unassignedList.users = dataProvider;
|
||||
unassignedList.roomName = ResourceUtil.getInstance().getString('bbb.users.breakout.notAssigned');
|
||||
unassignedList.usersList.accessibilityName = ResourceUtil.getInstance().getString('bbb.users.breakout.notAssigned');
|
||||
unassignedList.notAssignedUsers = true;
|
||||
}
|
||||
|
||||
@ -274,7 +279,8 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
<mx:HBox id="roomsBox" paddingTop="20" width="100%">
|
||||
<mx:HBox width="50%" height="100%" horizontalAlign="left" verticalAlign="middle">
|
||||
<mx:Label text="{ResourceUtil.getInstance().getString('bbb.users.breakout.rooms')}" />
|
||||
<mx:ComboBox id="roomsCombo" width="80%" change="{assignUsersForCreation()}" dataProvider="{roomsProvider}"/>
|
||||
<mx:ComboBox id="roomsCombo" width="80%" change="{assignUsersForCreation()}" dataProvider="{roomsProvider}"
|
||||
accessibilityName="{ResourceUtil.getInstance().getString('bbb.users.breakout.roomsCombo.accessibilityName')}" />
|
||||
</mx:HBox>
|
||||
<!-- <mx:Button paddingLeft="20" width="50%" id="randomAssignBtn"
|
||||
label="{ResourceUtil.getInstance().getString('bbb.users.breakout.randomAssign')}" click="assignUsers()"/> -->
|
||||
@ -282,13 +288,15 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
<mx:HBox id="durationBox" width="100%" paddingTop="12">
|
||||
<mx:Label text="{ResourceUtil.getInstance().getString('bbb.users.breakout.timeLimit')}" />
|
||||
<mx:NumericStepper id="durationStepper" value="15" minimum="1" maximum="600"/>
|
||||
<mx:NumericStepper id="durationStepper" value="15" minimum="1" maximum="600"
|
||||
accessibilityName="{ResourceUtil.getInstance().getString('bbb.users.breakout.durationStepper.accessibilityName')}"/>
|
||||
<mx:Label text="{ResourceUtil.getInstance().getString('bbb.users.breakout.minutes')}"/>
|
||||
</mx:HBox>
|
||||
|
||||
<mx:HBox id="recordBox" width="100%" paddingTop="12">
|
||||
<mx:Label text="{ResourceUtil.getInstance().getString('bbb.users.breakout.record')}" visible="false" />
|
||||
<mx:CheckBox id="recordCheckbox" visible="false" />
|
||||
<mx:CheckBox id="recordCheckbox" visible="false"
|
||||
accessibilityName="{ResourceUtil.getInstance().getString('bbb.users.breakout.recordCheckbox.accessibilityName')}"/>
|
||||
</mx:HBox>
|
||||
<mx:Tile id="roomsContainer" styleName="roomsContainer" width="100%" height="100%"/>
|
||||
|
||||
|
@ -600,11 +600,12 @@
|
||||
]]>
|
||||
</mx:Script>
|
||||
|
||||
<mdi:TabIndexer id="tabIndexer" startIndex="{partOptions.baseTabIndex + 5}" tabIndices="{[usersGrid, roomsGrid, closeRoomsBtn, emojiStatusBtn, settingsBtn]}"/>
|
||||
|
||||
<mx:DataGrid id="usersGrid" dataProvider="{users}" editable="false" sortableColumns="true"
|
||||
dragEnabled="false" width="100%" height="100%" draggableColumns="false"
|
||||
itemRollOver="onItemRollOver(event)"
|
||||
itemRollOut="onItemRollOut(event)"
|
||||
tabIndex="{partOptions.baseTabIndex+5}"
|
||||
accessibilityName="{ResourceUtil.getInstance().getString('bbb.users.usersGrid.accessibilityName')}" >
|
||||
<mx:columns>
|
||||
<mx:DataGridColumn dataField="userStatus" headerText="{ResourceUtil.getInstance().getString('bbb.users.usersGrid.statusItemRenderer')}" editable="false" width="45" minWidth="45"
|
||||
@ -622,12 +623,13 @@
|
||||
horizontalScrollPolicy="off"
|
||||
width="100%" height="180">
|
||||
<mx:HBox width="100%">
|
||||
<mx:Label text="{ResourceUtil.getInstance().getString('bbb.users.breakout.breakoutRooms')}"/>
|
||||
<mx:Label width="100%" textAlign="right" id="breakoutTimeLabel" text="..."/>
|
||||
<mx:Label styleName="breakoutRoomUserWindowHeadingStyle" text="{ResourceUtil.getInstance().getString('bbb.users.breakout.breakoutRooms')}"/>
|
||||
<mx:Label styleName="breakoutRoomUserWindowHeadingStyle" width="100%" textAlign="right" id="breakoutTimeLabel" text="..."/>
|
||||
</mx:HBox>
|
||||
|
||||
<mx:DataGrid id="roomsGrid" editable="false" sortableColumns="false" dataProvider="{breakoutRoomsList}"
|
||||
dragEnabled="false" width="100%" height="100%" draggableColumns="false">
|
||||
dragEnabled="false" width="100%" height="100%" draggableColumns="false"
|
||||
accessibilityName="{ResourceUtil.getInstance().getString('bbb.users.breakout.breakoutRooms')}">
|
||||
<mx:columns>
|
||||
<mx:DataGridColumn labelFunction="breakoutRoomNameLabelFunction" headerText="{ResourceUtil.getInstance().getString('bbb.users.roomsGrid.room')}" />
|
||||
<mx:DataGridColumn dataField="numberOfUsers" headerText="{ResourceUtil.getInstance().getString('bbb.users.roomsGrid.users')}"/>
|
||||
@ -645,9 +647,9 @@
|
||||
<mx:Button id="emojiStatusBtn" icon="{images.emoji_raiseHand}" width="30" height="30"
|
||||
accessibilityName="{ResourceUtil.getInstance().getString('bbb.users.emojiStatusBtn.toolTip')}"
|
||||
toolTip="{ResourceUtil.getInstance().getString('bbb.users.emojiStatusBtn.toolTip')}" click="openEmojiStatusMenu()"
|
||||
visible="true" tabIndex="{partOptions.baseTabIndex+10}" />
|
||||
visible="true" />
|
||||
<mx:Button id="settingsBtn" icon="{images.users_settings}" width="30" height="30"
|
||||
toolTip="{ResourceUtil.getInstance().getString('bbb.users.settings.buttonTooltip')}" click="openSettings()" visible="true" tabIndex="{partOptions.baseTabIndex+15}" />
|
||||
toolTip="{ResourceUtil.getInstance().getString('bbb.users.settings.buttonTooltip')}" click="openSettings()" visible="true" />
|
||||
<mx:VBox>
|
||||
<mx:Label text="{ResourceUtil.getInstance().getString('bbb.users.roomMuted.text')}" visible="{roomMuted}" includeInLayout="{roomMuted}" />
|
||||
<mx:Label text="{ResourceUtil.getInstance().getString('bbb.users.roomLocked.text')}" visible="{roomLocked}" includeInLayout="{roomLocked}" />
|
||||
|
@ -19,6 +19,7 @@
|
||||
# Author(s):
|
||||
# Fred Dixon <ffdixon@bigbluebutton.org>
|
||||
# Gustavo Salazar <guga.salazar.loor@gmail.com>
|
||||
# Ghazi Triki <ghazi.nocturne@gmail.com>
|
||||
#
|
||||
# Changelog:
|
||||
# 2011-08-18 FFD Inital Version
|
||||
@ -32,6 +33,7 @@
|
||||
# 2013-04-05 GUG Description is optional in bbb-record --watch
|
||||
# 2013-04-05 GUG Map internal meeting id with external meeting id
|
||||
# 2016-07-02 FFD Updates for 1.1
|
||||
# 2016-09-27 GTR Stricter recording directories names detection to cover breakout rooms recordings
|
||||
|
||||
#set -e
|
||||
#set -x
|
||||
@ -406,7 +408,7 @@ if [ $DELETEALL ]; then
|
||||
rm -f /var/bigbluebutton/screenshare/*.flv
|
||||
rm -f /var/freeswitch/meetings/*.wav
|
||||
|
||||
for meeting in $(ls /var/bigbluebutton | grep "[0-9]\{13\}$"); do
|
||||
for meeting in $(ls /var/bigbluebutton | grep "^[0-9a-f]\{40\}-[[:digit:]]\{13\}-\?[[:digit:]]\{1\}\?$"); do
|
||||
echo "deleting: $meeting"
|
||||
rm -rf /var/bigbluebutton/$meeting
|
||||
done
|
||||
@ -446,10 +448,10 @@ if [ -z $HEAD ]; then
|
||||
fi
|
||||
|
||||
tmp_file=$(mktemp)
|
||||
ls -t /var/bigbluebutton | grep "[0-9]\{13\}$" | head -n $HEAD > $tmp_file
|
||||
ls -t /var/bigbluebutton/recording/raw | grep "[0-9]\{13\}$" | head -n $HEAD >> $tmp_file
|
||||
ls -t /var/bigbluebutton | grep "^[0-9a-f]\{40\}-[[:digit:]]\{13\}-\?[[:digit:]]\{1\}\?$" | head -n $HEAD > $tmp_file
|
||||
ls -t /var/bigbluebutton/recording/raw | grep "^[0-9a-f]\{40\}-[[:digit:]]\{13\}-\?[[:digit:]]\{1\}\?$" | head -n $HEAD >> $tmp_file
|
||||
|
||||
#for meeting in $(ls -t /var/bigbluebutton | grep "[0-9]\{13\}$" | head -n $HEAD); do
|
||||
#for meeting in $(ls -t /var/bigbluebutton | grep "^[0-9a-f]\{40\}-[[:digit:]]\{13\}-\?[[:digit:]]\{1\}\?$" | head -n $HEAD); do
|
||||
for meeting in $(cat $tmp_file | sort -t - -k 2 -r| uniq); do
|
||||
echo -n "$meeting"
|
||||
timestamp=$(echo $meeting | sed s/.*-//g)
|
||||
@ -471,7 +473,7 @@ for meeting in $(cat $tmp_file | sort -t - -k 2 -r| uniq); do
|
||||
fi
|
||||
|
||||
#
|
||||
# Check if there area uploaded presentations
|
||||
# Check if there is any uploaded presentations
|
||||
if [ -d $RAW_PRESENTATION_SRC/$meeting/$meeting ]; then
|
||||
if [ "$(ls -A $RAW_PRESENTATION_SRC/$meeting/$meeting)" ]; then
|
||||
echo -n "X"
|
||||
@ -548,7 +550,7 @@ for meeting in $(cat $tmp_file | sort -t - -k 2 -r| uniq); do
|
||||
done
|
||||
|
||||
#
|
||||
# Numbe rof slides
|
||||
# Number of slides
|
||||
if [ -d /var/bigbluebutton/$meeting/$meeting ]; then
|
||||
printf "%7s" $(find /var/bigbluebutton/$meeting/$meeting -name "*.swf" | wc -l)
|
||||
else
|
||||
@ -562,7 +564,7 @@ for meeting in $(cat $tmp_file | sort -t - -k 2 -r| uniq); do
|
||||
recording=$meeting
|
||||
|
||||
#
|
||||
# Checka processed
|
||||
# Check processed
|
||||
processed=""
|
||||
for type in $TYPES; do
|
||||
if [ -f $STATUS/processed/$recording-$type.done ]; then
|
||||
|
@ -26,17 +26,24 @@
|
||||
"app.failedMessage": "Apologies, trouble connecting to the server.",
|
||||
"app.connectingMessage": "Connecting...",
|
||||
"app.waitingMessage": "Disconnected. Trying to reconnect in {seconds} seconds...",
|
||||
"app.dropdown.options": "Options",
|
||||
"app.dropdown.fullscreenLabel": "Make fullscreen",
|
||||
"app.dropdown.settingsLabel": "Open settings",
|
||||
"app.dropdown.leaveSessionLabel": "Logout",
|
||||
"app.dropdown.fullscreenDesc": "Make the settings menu fullscreen",
|
||||
"app.dropdown.settingsDesc": "Change the general settings",
|
||||
"app.dropdown.leaveSessionDesc": "Leave the meeting",
|
||||
"app.navBar.settingsDropdown.optionsLabel": "Options",
|
||||
"app.navBar.settingsDropdown.fullscreenLabel": "Make fullscreen",
|
||||
"app.navBar.settingsDropdown.settingsLabel": "Open settings",
|
||||
"app.navBar.settingsDropdown.leaveSessionLabel": "Logout",
|
||||
"app.navBar.settingsDropdown.fullscreenDesc": "Make the settings menu fullscreen",
|
||||
"app.navBar.settingsDropdown.settingsDesc": "Change the general settings",
|
||||
"app.navBar.settingsDropdown.leaveSessionDesc": "Leave the meeting",
|
||||
"app.leaveConfirmation.title": "Leave Session",
|
||||
"app.leaveConfirmation.message": "Do you want to leave this meeting?",
|
||||
"app.leaveConfirmation.confirmLabel": "Leave",
|
||||
"app.leaveConfirmation.confirmDesc": "Logs you out of the meeting",
|
||||
"app.leaveConfirmation.dismissLabel": "Cancel",
|
||||
"app.leaveConfirmation.dismissDesc": "Closes and rejects the leave confirmation"
|
||||
"app.leaveConfirmation.dismissDesc": "Closes and rejects the leave confirmation",
|
||||
"app.actionsBar.actionsDropdown.actionsLabel": "Actions",
|
||||
"app.actionsBar.actionsDropdown.presentationLabel": "Upload a presentation",
|
||||
"app.actionsBar.actionsDropdown.initPollLabel": "Initiate a poll",
|
||||
"app.actionsBar.actionsDropdown.desktopShareLabel": "Share your screen",
|
||||
"app.actionsBar.actionsDropdown.presentationDesc": "Upload your presentation",
|
||||
"app.actionsBar.actionsDropdown.initPollDesc": "Initiate a poll",
|
||||
"app.actionsBar.actionsDropdown.desktopShareDesc": "Share your screen with others"
|
||||
}
|
||||
|
@ -0,0 +1,97 @@
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
|
||||
import Button from '/imports/ui/components/button/component';
|
||||
import Dropdown from '/imports/ui/components/dropdown/component';
|
||||
import DropdownTrigger from '/imports/ui/components/dropdown/trigger/component';
|
||||
import DropdownContent from '/imports/ui/components/dropdown/content/component';
|
||||
import DropdownList from '/imports/ui/components/dropdown/list/component';
|
||||
import DropdownListItem from '/imports/ui/components/dropdown/list/item/component';
|
||||
|
||||
const intlMessages = defineMessages({
|
||||
actionsLabel: {
|
||||
id: 'app.actionsBar.actionsDropdown.actionsLabel',
|
||||
defaultMessage: 'Actions',
|
||||
},
|
||||
presentationLabel: {
|
||||
id: 'app.actionsBar.actionsDropdown.presentationLabel',
|
||||
defaultMessage: 'Upload a presentation',
|
||||
},
|
||||
initPollLabel: {
|
||||
id: 'app.actionsBar.actionsDropdown.initPollLabel',
|
||||
defaultMessage: 'Initiate a poll',
|
||||
},
|
||||
desktopShareLabel: {
|
||||
id: 'app.actionsBar.actionsDropdown.desktopShareLabel',
|
||||
defaultMessage: 'Share your screen',
|
||||
},
|
||||
presentationDesc: {
|
||||
id: 'app.actionsBar.actionsDropdown.presentationDesc',
|
||||
defaultMessage: 'Upload your presentation',
|
||||
},
|
||||
initPollDesc: {
|
||||
id: 'app.actionsBar.actionsDropdown.initPollDesc',
|
||||
defaultMessage: 'Initiate a poll',
|
||||
},
|
||||
desktopShareDesc: {
|
||||
id: 'app.actionsBar.actionsDropdown.desktopShareDesc',
|
||||
defaultMessage: 'Share your screen with others',
|
||||
},
|
||||
});
|
||||
|
||||
const presentation = () => {console.log('Should show the uploader component');};
|
||||
|
||||
const polling = () => {console.log('Should initiate a polling');};
|
||||
|
||||
const shareScreen = () => {console.log('Should start screen sharing');};
|
||||
|
||||
class ActionsDropdown extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { intl } = this.props;
|
||||
return (
|
||||
<Dropdown ref="dropdown">
|
||||
<DropdownTrigger>
|
||||
<Button
|
||||
label={intl.formatMessage(intlMessages.actionsLabel)}
|
||||
icon="circle-add"
|
||||
color="primary"
|
||||
size="lg"
|
||||
circle={true}
|
||||
onClick={() => null}
|
||||
/>
|
||||
</DropdownTrigger>
|
||||
<DropdownContent placement="top left">
|
||||
<DropdownList>
|
||||
<DropdownListItem
|
||||
icon="presentation"
|
||||
label={intl.formatMessage(intlMessages.presentationLabel)}
|
||||
description={intl.formatMessage(intlMessages.presentationDesc)}
|
||||
onClick={presentation.bind(this)}
|
||||
/>
|
||||
|
||||
{/* These icons are unaligned because of the font issue
|
||||
Check it later */}
|
||||
<DropdownListItem
|
||||
icon="polling"
|
||||
label={intl.formatMessage(intlMessages.initPollLabel)}
|
||||
description={intl.formatMessage(intlMessages.initPollDesc)}
|
||||
onClick={polling.bind(this)}
|
||||
/>
|
||||
<DropdownListItem
|
||||
icon="desktop"
|
||||
label={intl.formatMessage(intlMessages.desktopShareLabel)}
|
||||
description={intl.formatMessage(intlMessages.desktopShareDesc)}
|
||||
onClick={shareScreen.bind(this)}
|
||||
/>
|
||||
</DropdownList>
|
||||
</DropdownContent>
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default injectIntl(ActionsDropdown);
|
@ -3,6 +3,8 @@ import styles from './styles.scss';
|
||||
|
||||
import Button from '../button/component';
|
||||
|
||||
import ActionsDropdown from './actions-dropdown/component';
|
||||
|
||||
export default class ActionsBar extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
@ -15,14 +17,7 @@ export default class ActionsBar extends Component {
|
||||
return (
|
||||
<div className={styles.actionsbar}>
|
||||
<div className={styles.left}>
|
||||
<Button
|
||||
onClick={this.handleClick}
|
||||
label={'Actions'}
|
||||
color={'primary'}
|
||||
icon={'circle-add'}
|
||||
size={'lg'}
|
||||
circle={true}
|
||||
/>
|
||||
<ActionsDropdown />
|
||||
</div>
|
||||
<div className={styles.center}>
|
||||
<Button
|
||||
|
@ -101,6 +101,10 @@ const showModal = (val) => {
|
||||
}
|
||||
};
|
||||
|
||||
const clearModal = () => {
|
||||
showModal(null);
|
||||
};
|
||||
|
||||
export {
|
||||
subscribeForData,
|
||||
setCredentials,
|
||||
@ -110,4 +114,5 @@ export {
|
||||
redirectToLogoutUrl,
|
||||
getModal,
|
||||
showModal,
|
||||
clearModal,
|
||||
};
|
||||
|
@ -41,6 +41,7 @@ const propTypes = {
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
isOpen: false,
|
||||
};
|
||||
|
||||
export default class Dropdown extends Component {
|
||||
|
@ -1,4 +1,5 @@
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { clearModal } from '/imports/ui/components/app/service';
|
||||
import ModalBase from './base/component';
|
||||
import Button from '../button/component';
|
||||
import styles from './styles.scss';
|
||||
@ -45,6 +46,7 @@ export default class Modal extends Component {
|
||||
|
||||
handleDismiss() {
|
||||
this.setState({ isOpen: false });
|
||||
clearModal();
|
||||
}
|
||||
|
||||
handleConfirm() {
|
||||
|
@ -1,14 +1,11 @@
|
||||
import React, { Component, PropTyes } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import ReactDOM from 'react-dom';
|
||||
import classNames from 'classnames';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import styles from '../styles';
|
||||
|
||||
import { showModal } from '/imports/ui/components/app/service';
|
||||
import LogoutConfirmation from '/imports/ui/components/logout-confirmation/component';
|
||||
import Settings from '/imports/ui/components/settings/component';
|
||||
|
||||
import Icon from '/imports/ui/components/icon/component';
|
||||
import Button from '/imports/ui/components/button/component';
|
||||
import Dropdown from '/imports/ui/components/dropdown/component';
|
||||
import DropdownTrigger from '/imports/ui/components/dropdown/trigger/component';
|
||||
@ -17,6 +14,37 @@ import DropdownList from '/imports/ui/components/dropdown/list/component';
|
||||
import DropdownListItem from '/imports/ui/components/dropdown/list/item/component';
|
||||
import DropdownListSeparator from '/imports/ui/components/dropdown/list/separator/component';
|
||||
|
||||
const intlMessages = defineMessages({
|
||||
optionsLabel: {
|
||||
id: 'app.navBar.settingsDropdown.optionsLabel',
|
||||
defaultMessage: 'Options',
|
||||
},
|
||||
fullscreenLabel: {
|
||||
id: 'app.navBar.settingsDropdown.fullscreenLabel',
|
||||
defaultMessage: 'Make fullscreen',
|
||||
},
|
||||
settingsLabel: {
|
||||
id: 'app.navBar.settingsDropdown.settingsLabel',
|
||||
defaultMessage: 'Open settings',
|
||||
},
|
||||
leaveSessionLabel: {
|
||||
id: 'app.navBar.settingsDropdown.leaveSessionLabel',
|
||||
defaultMessage: 'Logout',
|
||||
},
|
||||
fullscreenDesc: {
|
||||
id: 'app.navBar.settingsDropdown.fullscreenDesc',
|
||||
defaultMessage: 'Make the settings menu fullscreen',
|
||||
},
|
||||
settingsDesc: {
|
||||
id: 'app.navBar.settingsDropdown.settingsDesc',
|
||||
defaultMessage: 'Change the general settings',
|
||||
},
|
||||
leaveSessionDesc: {
|
||||
id: 'app.navBar.settingsDropdown.leaveSessionDesc',
|
||||
defaultMessage: 'Leave the meeting',
|
||||
},
|
||||
});
|
||||
|
||||
const toggleFullScreen = () => {
|
||||
let element = document.documentElement;
|
||||
|
||||
@ -47,18 +75,18 @@ const openSettings = () => showModal(<Settings />);
|
||||
|
||||
const openLogoutConfirmation = () => showModal(<LogoutConfirmation />);
|
||||
|
||||
export default class SettingsDropdown extends Component {
|
||||
class SettingsDropdown extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { intl } = this.props;
|
||||
return (
|
||||
<Dropdown ref="dropdown">
|
||||
<DropdownTrigger>
|
||||
<Button
|
||||
role="button"
|
||||
label="Settings"
|
||||
label={intl.formatMessage(intlMessages.optionsLabel)}
|
||||
icon="more"
|
||||
ghost={true}
|
||||
circle={true}
|
||||
@ -73,22 +101,22 @@ export default class SettingsDropdown extends Component {
|
||||
<DropdownContent placement="bottom right">
|
||||
<DropdownList>
|
||||
<DropdownListItem
|
||||
icon="full-screen"
|
||||
label="Fullscreen"
|
||||
defaultMessage="Make the application fullscreen"
|
||||
icon="fullscreen"
|
||||
label={intl.formatMessage(intlMessages.fullscreenLabel)}
|
||||
description={intl.formatMessage(intlMessages.fullscreenDesc)}
|
||||
onClick={toggleFullScreen.bind(this)}
|
||||
/>
|
||||
<DropdownListItem
|
||||
icon="more"
|
||||
label="Settings"
|
||||
description="Change the general settings"
|
||||
label={intl.formatMessage(intlMessages.settingsLabel)}
|
||||
description={intl.formatMessage(intlMessages.settingsDesc)}
|
||||
onClick={openSettings.bind(this)}
|
||||
/>
|
||||
<DropdownListSeparator />
|
||||
<DropdownListItem
|
||||
icon="logout"
|
||||
label="Leave Session"
|
||||
description="Leave the meeting"
|
||||
label={intl.formatMessage(intlMessages.leaveSessionLabel)}
|
||||
description={intl.formatMessage(intlMessages.leaveSessionDesc)}
|
||||
onClick={openLogoutConfirmation.bind(this)}
|
||||
/>
|
||||
</DropdownList>
|
||||
@ -97,3 +125,5 @@ export default class SettingsDropdown extends Component {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default injectIntl(SettingsDropdown);
|
||||
|
@ -21,7 +21,7 @@ dependencies {
|
||||
compile 'commons-io:commons-io:2.4'
|
||||
compile 'com.google.code.gson:gson:1.7.1'
|
||||
compile 'commons-httpclient:commons-httpclient:3.1'
|
||||
compile 'com.zaxxer:nuprocess:1.0.4'
|
||||
compile 'com.zaxxer:nuprocess:1.1.0'
|
||||
|
||||
compile 'org.bigbluebutton:bbb-common-message:0.0.18-SNAPSHOT'
|
||||
|
||||
|
@ -58,8 +58,9 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
<bean id="presDownloadService" class="org.bigbluebutton.presentation.PresentationUrlDownloadService">
|
||||
<property name="presentationDir" value="${presentationDir}"/>
|
||||
<property name="presentationBaseURL" value="${presentationBaseURL}"/>
|
||||
<property name="documentConversionService" ref="documentConversionService"/>
|
||||
<property name="presentationBaseURL" value="${presentationBaseURL}"/>
|
||||
<property name="pageExtractor" ref="pageExtractor"/>
|
||||
<property name="documentConversionService" ref="documentConversionService"/>
|
||||
</bean>
|
||||
|
||||
<bean id="recordingService" class="org.bigbluebutton.api.RecordingService" >
|
||||
|
@ -19,6 +19,7 @@
|
||||
package org.bigbluebutton.web.controllers
|
||||
|
||||
import javax.servlet.ServletRequest;
|
||||
|
||||
import java.net.URI;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
@ -28,7 +29,9 @@ import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.apache.commons.lang.RandomStringUtils;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.bigbluebutton.api.domain.Config;
|
||||
@ -865,6 +868,7 @@ class ApiController {
|
||||
for (m in mtgs) {
|
||||
meeting {
|
||||
meetingID() { mkp.yield(m.getExternalId()) }
|
||||
internalMeetingID() { mkp.yield(m.getInternalId()) }
|
||||
isBreakout() { mkp.yield(m.isBreakout()) }
|
||||
meetingName() { mkp.yield(m.getName()) }
|
||||
createTime(m.getCreateTime())
|
||||
@ -2057,9 +2061,8 @@ class ApiController {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def processDocumentFromRawBytes(bytes, presFilename, meetingId) {
|
||||
def filenameExt = Util.getFilenameExt(presFilename);
|
||||
def filenameExt = FilenameUtils.getExtension(presFilename);
|
||||
String presentationDir = presentationService.getPresentationDir()
|
||||
def presId = Util.generatePresentationId(presFilename)
|
||||
File uploadDir = Util.createPresentationDirectory(meetingId, presentationDir, presId)
|
||||
@ -2080,13 +2083,13 @@ class ApiController {
|
||||
def downloadAndProcessDocument(address, meetingId) {
|
||||
log.debug("ApiController#downloadAndProcessDocument(${address}, ${meetingId})");
|
||||
String presFilename = address.tokenize("/")[-1];
|
||||
def filenameExt = presDownloadService.getFilenameExt(presFilename);
|
||||
def filenameExt = FilenameUtils.getExtension(presFilename);
|
||||
String presentationDir = presentationService.getPresentationDir()
|
||||
|
||||
def presId = presDownloadService.generatePresentationId(presFilename)
|
||||
File uploadDir = presDownloadService.createPresentationDirectory(meetingId, presentationDir, presId)
|
||||
if (uploadDir != null) {
|
||||
def newFilename = presDownloadService.createNewFilename(presId, filenameExt)
|
||||
def newFilename = Util.createNewFilename(presId, filenameExt)
|
||||
def newFilePath = uploadDir.absolutePath + File.separatorChar + newFilename
|
||||
|
||||
if (presDownloadService.savePresentation(meetingId, newFilePath, address)) {
|
||||
@ -2192,6 +2195,7 @@ class ApiController {
|
||||
response() {
|
||||
returncode(RESP_CODE_SUCCESS)
|
||||
meetingID() { mkp.yield(meeting.getExternalId()) }
|
||||
internalMeetingID() { mkp.yield(meeting.getInternalId()) }
|
||||
attendeePW() { mkp.yield(meeting.getViewerPassword()) }
|
||||
moderatorPW() { mkp.yield(meeting.getModeratorPassword()) }
|
||||
createTime(meeting.getCreateTime())
|
||||
|
@ -19,6 +19,8 @@
|
||||
package org.bigbluebutton.web.controllers
|
||||
|
||||
import grails.converters.*
|
||||
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.bigbluebutton.web.services.PresentationService
|
||||
import org.bigbluebutton.presentation.UploadedPresentation
|
||||
import org.bigbluebutton.api.MeetingService;
|
||||
@ -48,7 +50,7 @@ class PresentationController {
|
||||
if (file && !file.empty) {
|
||||
flash.message = 'Your file has been uploaded'
|
||||
def presFilename = file.getOriginalFilename()
|
||||
def filenameExt = Util.getFilenameExt(presFilename);
|
||||
def filenameExt = FilenameUtils.getExtension(presFilename);
|
||||
String presentationDir = presentationService.getPresentationDir()
|
||||
def presId = Util.generatePresentationId(presFilename)
|
||||
File uploadDir = Util.createPresentationDirectory(meetingId, presentationDir, presId)
|
||||
|
@ -19,12 +19,18 @@
|
||||
|
||||
package org.bigbluebutton.api;
|
||||
|
||||
import groovy.json.JsonBuilder;
|
||||
import groovy.util.XmlSlurper;
|
||||
import groovy.util.slurpersupport.Attributes;
|
||||
import groovy.util.slurpersupport.GPathResult;
|
||||
import groovy.xml.MarkupBuilder;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.bigbluebutton.api.domain.Extension;
|
||||
import org.bigbluebutton.api.domain.Recording;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -44,7 +50,15 @@ public class RecordingServiceHelperImp implements RecordingServiceHelper {
|
||||
<processing_time>5429</processing_time>
|
||||
<duration>101014</duration>
|
||||
<extension>
|
||||
... Any XML element, to be passed through into playback format element.
|
||||
<!-- Any XML element, to be passed through into playback format element. -->
|
||||
<preview> <!-- The first level is the name of the extension -->
|
||||
<text>Instrument Flying</text>
|
||||
<images>
|
||||
<image width="176" height="136" alt="Instrument Flying">http://bbb-server/.../thumb-1.png</image>
|
||||
<image width="176" height="136" alt="Course Structure">http://bbb-server/.../thumb-2.png</image>
|
||||
<image width="176" height="136" alt="Requirements">http://bbb-server/.../thumb-3.png</image>
|
||||
</images>
|
||||
</preview>
|
||||
</extension>
|
||||
</playback>
|
||||
<meta>
|
||||
@ -59,7 +73,7 @@ public class RecordingServiceHelperImp implements RecordingServiceHelper {
|
||||
public void writeRecordingInfo(String path, Recording info) {
|
||||
def writer = new StringWriter()
|
||||
def builder = new groovy.xml.MarkupBuilder(writer)
|
||||
def metadataXml = builder.recording {
|
||||
builder.recording {
|
||||
builder.id(info.getId())
|
||||
builder.state(info.getState())
|
||||
builder.published(info.isPublished())
|
||||
@ -72,7 +86,17 @@ public class RecordingServiceHelperImp implements RecordingServiceHelper {
|
||||
builder.format(info.getPlaybackFormat())
|
||||
builder.link(info.getPlaybackLink())
|
||||
builder.duration(info.getPlaybackDuration())
|
||||
builder.extension(info.getPlaybackExtensions())
|
||||
def extensions = info.getPlaybackExtensions()
|
||||
if ( !extensions.isEmpty() ) {
|
||||
builder.extensions {
|
||||
extensions.each { extension ->
|
||||
def extensionType = extension.getType()
|
||||
builder."${extensionType}"{
|
||||
extensionPropertiesToXML(builder, extension.getProperties())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Map<String,String> metainfo = info.getMetadata();
|
||||
@ -92,7 +116,7 @@ public class RecordingServiceHelperImp implements RecordingServiceHelper {
|
||||
File file = new File(dir.getPath() + File.separatorChar + "metadata.xml");
|
||||
if ( file ) {
|
||||
def recording = new XmlSlurper().parse(file);
|
||||
return getInfo(recording);
|
||||
return getInfo(recording);
|
||||
}
|
||||
} catch ( FileNotFoundException e) {
|
||||
// Do nothing, just return null
|
||||
@ -105,6 +129,7 @@ public class RecordingServiceHelperImp implements RecordingServiceHelper {
|
||||
|
||||
private Recording getInfo(GPathResult rec) {
|
||||
Recording r = new Recording();
|
||||
//Add basic information
|
||||
r.setId(rec.id.text());
|
||||
r.setState(rec.state.text());
|
||||
r.setPublished(Boolean.parseBoolean(rec.published.text()));
|
||||
@ -116,11 +141,14 @@ public class RecordingServiceHelperImp implements RecordingServiceHelper {
|
||||
r.setPlaybackDuration(rec.playback.duration.text());
|
||||
}
|
||||
|
||||
/*
|
||||
Commenting this out to see if this is causing memory to hang around resulting in
|
||||
OOM in tomcat7 (ralam july 23, 2015)
|
||||
r.setPlaybackExtensions(rec.playback.extension.children());
|
||||
*/
|
||||
//Add extensions
|
||||
List<Extension> extensions = new ArrayList<Extension>()
|
||||
rec.playback.extensions.children().each { extension ->
|
||||
extensions.add( new Extension(extension.name(), extensionPropertiesToMap(extension)) )
|
||||
}
|
||||
r.setPlaybackExtensions(extensions)
|
||||
|
||||
//Add metadata
|
||||
Map<String, String> meta = new HashMap<String, String>();
|
||||
rec.meta.children().each { anode ->
|
||||
meta.put(anode.name().toString(), anode.text().toString());
|
||||
@ -129,4 +157,93 @@ public class RecordingServiceHelperImp implements RecordingServiceHelper {
|
||||
return r;
|
||||
}
|
||||
|
||||
private Map<String, ?> processNode( Map<String, ?> map, node) {
|
||||
if ( !map[node.name()] ) {
|
||||
map[node.name()] = map.getClass().newInstance()
|
||||
}
|
||||
Map<String, ?> nodeMap = map[node.name()]
|
||||
node.children().each { it ->
|
||||
if (it.children().size() == 0) {
|
||||
processLeaf( nodeMap, it)
|
||||
} else {
|
||||
processNode( nodeMap, it)
|
||||
}
|
||||
}
|
||||
nodeMap
|
||||
}
|
||||
|
||||
private Map<String, ?> processLeaf(Map<String, ?> map, node) {
|
||||
//Initialize map for node content
|
||||
Map<String, ?> nodeContent = [ : ]
|
||||
//Assign node content text
|
||||
nodeContent["text"] = node.text()
|
||||
//Assign node content attributes (if any)
|
||||
Map attributes = node.attributes()
|
||||
if( attributes.size() > 0 ) {
|
||||
nodeContent["attributes"] = [ : ]
|
||||
attributes.each { attribute ->
|
||||
nodeContent["attributes"][attribute.getKey()] = attribute.getValue()
|
||||
}
|
||||
}
|
||||
//Add content to the node
|
||||
if ( map[node.name()] == null ) {
|
||||
map[node.name()] = nodeContent
|
||||
} else {
|
||||
if ( ! (map[node.name()] instanceof List) ) {
|
||||
map[node.name()] = [ map[node.name()] ]
|
||||
}
|
||||
map[node.name()] << nodeContent
|
||||
}
|
||||
map
|
||||
}
|
||||
|
||||
private Map<String, ?> extensionPropertiesToMap(GPathResult xml) {
|
||||
Map<String, ?> map = [ : ]
|
||||
xml.children().each {
|
||||
if ( it.children().size() == 0 ) {
|
||||
processLeaf(map, it)
|
||||
} else {
|
||||
processNode(map, it)
|
||||
}
|
||||
}
|
||||
map
|
||||
}
|
||||
|
||||
private void processMap(builder, node) {
|
||||
node.each { key, value ->
|
||||
if ( value instanceof Collection ) {
|
||||
processCollection(builder, key, value)
|
||||
} else if ( value instanceof Map ) {
|
||||
if ( value.containsKey("text") && value.containsKey("attributes") ) {
|
||||
builder."${key}"(value["attributes"], value["text"])
|
||||
} else {
|
||||
builder."${key}" {
|
||||
processMap(builder, value)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
builder."${key}"(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void processCollection(builder, nodesKey, nodes) {
|
||||
nodes.each { node ->
|
||||
processMap(builder, [ "${nodesKey}" : node ])
|
||||
}
|
||||
}
|
||||
|
||||
private void extensionPropertiesToXML(builder, properties) {
|
||||
properties.each { propertyKey, propertyValue ->
|
||||
if ( propertyValue instanceof Collection ) {
|
||||
builder."${propertyKey}" {
|
||||
processCollection(builder, propertyKey, propertyValue)
|
||||
}
|
||||
} else {
|
||||
builder."${propertyKey}" {
|
||||
processMap(builder, propertyValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -113,7 +113,8 @@ public class MeetingService implements MessageListener {
|
||||
}
|
||||
|
||||
public void registerUser(String meetingID, String internalUserId,
|
||||
String fullname, String role, String externUserID, String authToken, String avatarURL) {
|
||||
String fullname, String role, String externUserID,
|
||||
String authToken, String avatarURL) {
|
||||
handle(new RegisterUser(meetingID, internalUserId, fullname, role,
|
||||
externUserID, authToken, avatarURL));
|
||||
}
|
||||
@ -142,19 +143,22 @@ public class MeetingService implements MessageListener {
|
||||
* Remove registered users who did not successfully joined the meeting.
|
||||
*/
|
||||
public void purgeRegisteredUsers() {
|
||||
for (AbstractMap.Entry<String, Meeting> entry : this.meetings.entrySet()) {
|
||||
for (AbstractMap.Entry<String, Meeting> entry : this.meetings
|
||||
.entrySet()) {
|
||||
Long now = System.nanoTime();
|
||||
Meeting meeting = entry.getValue();
|
||||
|
||||
ConcurrentMap<String, User> users = meeting.getUsersMap();
|
||||
|
||||
for (AbstractMap.Entry<String, Long> registeredUser : meeting.getRegisteredUsers().entrySet()) {
|
||||
for (AbstractMap.Entry<String, Long> registeredUser : meeting
|
||||
.getRegisteredUsers().entrySet()) {
|
||||
String registeredUserID = registeredUser.getKey();
|
||||
Long registeredUserDate = registeredUser.getValue();
|
||||
|
||||
long registrationTime = registeredUserDate.longValue();
|
||||
long elapsedTime = now - registrationTime;
|
||||
if ( elapsedTime >= 60000 && !users.containsKey(registeredUserID)) {
|
||||
if (elapsedTime >= 60000
|
||||
&& !users.containsKey(registeredUserID)) {
|
||||
meeting.userUnregistered(registeredUserID);
|
||||
}
|
||||
}
|
||||
@ -319,7 +323,8 @@ public class MeetingService implements MessageListener {
|
||||
m.getName(), m.isRecord(), m.getTelVoice(), m.getDuration(),
|
||||
m.getAutoStartRecording(), m.getAllowStartStopRecording(),
|
||||
m.getModeratorPassword(), m.getViewerPassword(),
|
||||
m.getCreateTime(), formatPrettyDate(m.getCreateTime()), m.isBreakout());
|
||||
m.getCreateTime(), formatPrettyDate(m.getCreateTime()),
|
||||
m.isBreakout());
|
||||
}
|
||||
|
||||
private String formatPrettyDate(Long timestamp) {
|
||||
@ -357,8 +362,10 @@ public class MeetingService implements MessageListener {
|
||||
int dashes = meetingId.split("-", -1).length - 1;
|
||||
for (String key : meetings.keySet()) {
|
||||
int keyDashes = key.split("-", -1).length - 1;
|
||||
if (dashes == 2 && key.equals(meetingId)
|
||||
|| (dashes < 2 && keyDashes < 2 && key.startsWith(meetingId))) {
|
||||
if (dashes == 2
|
||||
&& key.equals(meetingId)
|
||||
|| (dashes < 2 && keyDashes < 2 && key
|
||||
.startsWith(meetingId))) {
|
||||
return (Meeting) meetings.get(key);
|
||||
}
|
||||
}
|
||||
@ -394,10 +401,8 @@ public class MeetingService implements MessageListener {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Map<String, Recording> getRecordings(List<String> idList,
|
||||
List<String> states) {
|
||||
List<Recording> recsList = recordingService.getRecordings(idList,
|
||||
states);
|
||||
public Map<String, Recording> getRecordings(List<String> idList, List<String> states) {
|
||||
List<Recording> recsList = recordingService.getRecordings(idList, states);
|
||||
Map<String, Recording> recs = reorderRecordings(recsList);
|
||||
return recs;
|
||||
}
|
||||
@ -405,8 +410,7 @@ public class MeetingService implements MessageListener {
|
||||
public Map<String, Recording> filterRecordingsByMetadata(
|
||||
Map<String, Recording> recordings,
|
||||
Map<String, String> metadataFilters) {
|
||||
return recordingService.filterRecordingsByMetadata(recordings,
|
||||
metadataFilters);
|
||||
return recordingService.filterRecordingsByMetadata(recordings, metadataFilters);
|
||||
}
|
||||
|
||||
public Map<String, Recording> reorderRecordings(List<Recording> olds) {
|
||||
@ -423,9 +427,8 @@ public class MeetingService implements MessageListener {
|
||||
List<Playback> plays = new ArrayList<Playback>();
|
||||
|
||||
if (r.getPlaybackFormat() != null) {
|
||||
plays.add(new Playback(r.getPlaybackFormat(), r
|
||||
.getPlaybackLink(), getDurationRecording(
|
||||
r.getPlaybackDuration(), r.getEndTime(),
|
||||
plays.add(new Playback(r.getPlaybackFormat(), r.getPlaybackLink(),
|
||||
getDurationRecording(r.getPlaybackDuration(), r.getEndTime(),
|
||||
r.getStartTime()), r.getPlaybackExtensions()));
|
||||
}
|
||||
|
||||
@ -433,12 +436,9 @@ public class MeetingService implements MessageListener {
|
||||
map.put(r.getId(), r);
|
||||
} else {
|
||||
Recording rec = map.get(r.getId());
|
||||
rec.getPlaybacks().add(
|
||||
new Playback(r.getPlaybackFormat(),
|
||||
r.getPlaybackLink(), getDurationRecording(
|
||||
r.getPlaybackDuration(),
|
||||
r.getEndTime(), r.getStartTime()), r
|
||||
.getPlaybackExtensions()));
|
||||
rec.getPlaybacks().add(new Playback(r.getPlaybackFormat(), r.getPlaybackLink(),
|
||||
getDurationRecording(r.getPlaybackDuration(), r.getEndTime(),
|
||||
r.getStartTime()), r.getPlaybackExtensions()));
|
||||
}
|
||||
}
|
||||
|
||||
@ -470,21 +470,22 @@ public class MeetingService implements MessageListener {
|
||||
|
||||
public void setPublishRecording(List<String> idList, boolean publish) {
|
||||
for (String id : idList) {
|
||||
if (publish) {
|
||||
recordingService.changeState(id, Recording.STATE_PUBLISHED);
|
||||
} else {
|
||||
recordingService.changeState(id, Recording.STATE_UNPUBLISHED);
|
||||
}
|
||||
if (publish) {
|
||||
recordingService.changeState(id, Recording.STATE_PUBLISHED);
|
||||
} else {
|
||||
recordingService.changeState(id, Recording.STATE_UNPUBLISHED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void deleteRecordings(List<String> idList) {
|
||||
for (String id : idList) {
|
||||
recordingService.changeState(id, Recording.STATE_DELETED);
|
||||
recordingService.changeState(id, Recording.STATE_DELETED);
|
||||
}
|
||||
}
|
||||
|
||||
public void updateRecordings(List<String> idList, Map<String, String> metaParams) {
|
||||
public void updateRecordings(List<String> idList,
|
||||
Map<String, String> metaParams) {
|
||||
recordingService.updateMetaParams(idList, metaParams);
|
||||
}
|
||||
|
||||
@ -527,12 +528,14 @@ public class MeetingService implements MessageListener {
|
||||
params.put("voiceBridge", message.voiceConfId);
|
||||
params.put("duration", message.durationInMinutes.toString());
|
||||
params.put("record", message.record.toString());
|
||||
params.put("welcome", getMeeting(message.parentId).getWelcomeMessageTemplate());
|
||||
params.put("welcome", getMeeting(message.parentId)
|
||||
.getWelcomeMessageTemplate());
|
||||
|
||||
Map<String, String> parentMeetingMetadata = parentMeeting.getMetadata();
|
||||
Map<String, String> parentMeetingMetadata = parentMeeting
|
||||
.getMetadata();
|
||||
|
||||
String metaPrefix = "meta_";
|
||||
for (String key: parentMeetingMetadata.keySet()) {
|
||||
for (String key : parentMeetingMetadata.keySet()) {
|
||||
String metaName = metaPrefix + key;
|
||||
// Inject metadata from parent meeting into the breakout room.
|
||||
params.put(metaName, parentMeetingMetadata.get(key));
|
||||
@ -542,10 +545,12 @@ public class MeetingService implements MessageListener {
|
||||
|
||||
handleCreateMeeting(breakout);
|
||||
|
||||
presDownloadService.downloadAndProcessDocument(
|
||||
message.defaultPresentationURL, breakout.getInternalId());
|
||||
presDownloadService.extractPage(message.parentId,
|
||||
message.sourcePresentationId,
|
||||
message.sourcePresentationSlide, breakout.getInternalId());
|
||||
} else {
|
||||
log.error("Failed to create breakout room " + message.breakoutId + ". Parent meeting not found.");
|
||||
log.error("Failed to create breakout room " + message.breakoutId
|
||||
+ ".Reason: Parent meeting not found.");
|
||||
}
|
||||
}
|
||||
|
||||
@ -595,6 +600,8 @@ public class MeetingService implements MessageListener {
|
||||
|
||||
Gson gson = new Gson();
|
||||
String logStr = gson.toJson(logData);
|
||||
|
||||
log.info("Meeting restarted: data={}", logStr);
|
||||
} else {
|
||||
Map<String, Object> logData = new HashMap<String, Object>();
|
||||
logData.put("meetingId", m.getInternalId());
|
||||
@ -606,14 +613,14 @@ public class MeetingService implements MessageListener {
|
||||
logData.put("event", "meeting_restarted");
|
||||
logData.put("description", "Meeting has restarted.");
|
||||
|
||||
Gson gson = new Gson();
|
||||
String logStr = gson.toJson(logData);
|
||||
Gson gson = new Gson();
|
||||
String logStr = gson.toJson(logData);
|
||||
|
||||
log.info("Meeting restarted: data={}", logStr);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
log.info("Meeting restarted: data={}", logStr);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void meetingDestroyed(MeetingDestroyed message) {
|
||||
Meeting m = getMeeting(message.meetingId);
|
||||
@ -655,7 +662,7 @@ public class MeetingService implements MessageListener {
|
||||
logData.put("description", "Meeting has been destroyed.");
|
||||
|
||||
Gson gson = new Gson();
|
||||
String logStr = gson.toJson(logData);
|
||||
String logStr = gson.toJson(logData);
|
||||
|
||||
log.info("Meeting destroyed: data={}", logStr);
|
||||
|
||||
@ -688,7 +695,7 @@ public class MeetingService implements MessageListener {
|
||||
logData.put("description", "User had joined the meeting.");
|
||||
|
||||
Gson gson = new Gson();
|
||||
String logStr = gson.toJson(logData);
|
||||
String logStr = gson.toJson(logData);
|
||||
log.info("User joined meeting: data={}", logStr);
|
||||
|
||||
return;
|
||||
@ -738,47 +745,49 @@ public class MeetingService implements MessageListener {
|
||||
}
|
||||
}
|
||||
|
||||
private void updatedStatus(UserStatusChanged message) {
|
||||
Meeting m = getMeeting(message.meetingId);
|
||||
if (m != null) {
|
||||
User user = m.getUserById(message.userId);
|
||||
if(user != null){
|
||||
user.setStatus(message.status, message.value);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void handle(IMessage message) {
|
||||
receivedMessages.add(message);
|
||||
}
|
||||
|
||||
|
||||
public void setParamsProcessorUtil(ParamsProcessorUtil util) {
|
||||
this.paramsProcessorUtil = util;
|
||||
}
|
||||
|
||||
public void setPresDownloadService(PresentationUrlDownloadService presDownloadService) {
|
||||
this.presDownloadService = presDownloadService;
|
||||
}
|
||||
|
||||
private void processStunTurnInfoRequested (StunTurnInfoRequested message) {
|
||||
Set<StunServer> stuns = stunTurnService.getStunServers();
|
||||
log.info("\nhere are the stuns:");
|
||||
for(StunServer s : stuns) {
|
||||
log.info("a stun: " + s.url);
|
||||
private void updatedStatus(UserStatusChanged message) {
|
||||
Meeting m = getMeeting(message.meetingId);
|
||||
if (m != null) {
|
||||
User user = m.getUserById(message.userId);
|
||||
if (user != null) {
|
||||
user.setStatus(message.status, message.value);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
Set<TurnEntry> turns = stunTurnService.getStunAndTurnServersFor(message.internalUserId);
|
||||
log.info("\nhere are the (" + turns.size() +") turns for internalUserId:" + message.internalUserId);
|
||||
for(TurnEntry t : turns) {
|
||||
log.info("a turn: " + t.url + "username/pass=" + t.username + '/' + t.password);
|
||||
}
|
||||
messagingService.sendStunTurnInfo(message.meetingId, message.internalUserId, stuns, turns);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(IMessage message) {
|
||||
receivedMessages.add(message);
|
||||
}
|
||||
|
||||
public void setParamsProcessorUtil(ParamsProcessorUtil util) {
|
||||
this.paramsProcessorUtil = util;
|
||||
}
|
||||
|
||||
public void setPresDownloadService(
|
||||
PresentationUrlDownloadService presDownloadService) {
|
||||
this.presDownloadService = presDownloadService;
|
||||
}
|
||||
|
||||
private void processStunTurnInfoRequested(StunTurnInfoRequested message) {
|
||||
Set<StunServer> stuns = stunTurnService.getStunServers();
|
||||
log.info("\nhere are the stuns:");
|
||||
for (StunServer s : stuns) {
|
||||
log.info("a stun: " + s.url);
|
||||
}
|
||||
Set<TurnEntry> turns = stunTurnService
|
||||
.getStunAndTurnServersFor(message.internalUserId);
|
||||
log.info("\nhere are the (" + turns.size()
|
||||
+ ") turns for internalUserId:" + message.internalUserId);
|
||||
for (TurnEntry t : turns) {
|
||||
log.info("a turn: " + t.url + "username/pass=" + t.username + '/'
|
||||
+ t.password);
|
||||
}
|
||||
messagingService.sendStunTurnInfo(message.meetingId,
|
||||
message.internalUserId, stuns, turns);
|
||||
}
|
||||
|
||||
public void userJoinedVoice(UserJoinedVoice message) {
|
||||
Meeting m = getMeeting(message.meetingId);
|
||||
@ -886,7 +895,6 @@ public class MeetingService implements MessageListener {
|
||||
runExec.execute(task);
|
||||
}
|
||||
|
||||
|
||||
public void start() {
|
||||
log.info("Starting Meeting Service.");
|
||||
try {
|
||||
@ -936,7 +944,8 @@ public class MeetingService implements MessageListener {
|
||||
messagingService = mess;
|
||||
}
|
||||
|
||||
public void setExpiredMeetingCleanupTimerTask(ExpiredMeetingCleanupTimerTask c) {
|
||||
public void setExpiredMeetingCleanupTimerTask(
|
||||
ExpiredMeetingCleanupTimerTask c) {
|
||||
cleaner = c;
|
||||
cleaner.setMeetingService(this);
|
||||
cleaner.start();
|
||||
@ -946,11 +955,14 @@ public class MeetingService implements MessageListener {
|
||||
removeMeetingWhenEnded = s;
|
||||
}
|
||||
|
||||
public void setRegisteredUserCleanupTimerTask(RegisteredUserCleanupTimerTask c) {
|
||||
public void setRegisteredUserCleanupTimerTask(
|
||||
RegisteredUserCleanupTimerTask c) {
|
||||
registeredUserCleaner = c;
|
||||
registeredUserCleaner.setMeetingService(this);
|
||||
registeredUserCleaner.start();
|
||||
}
|
||||
|
||||
public void setStunTurnService(StunTurnService s) { stunTurnService = s; }
|
||||
public void setStunTurnService(StunTurnService s) {
|
||||
stunTurnService = s;
|
||||
}
|
||||
}
|
||||
|
@ -10,13 +10,9 @@ public final class Util {
|
||||
return DigestUtils.shaHex(name) + "-" + timestamp;
|
||||
}
|
||||
|
||||
public static String getFilenameExt(String filename) {
|
||||
return filename.substring(filename.lastIndexOf("."));
|
||||
}
|
||||
|
||||
public static String createNewFilename(String presId, String fileExt) {
|
||||
return presId + fileExt;
|
||||
}
|
||||
public static String createNewFilename(String presId, String fileExt) {
|
||||
return presId + "." + fileExt;
|
||||
}
|
||||
|
||||
public static File createPresentationDirectory(String meetingId, String presentationDir, String presentationId) {
|
||||
String meetingPath = presentationDir + File.separatorChar + meetingId + File.separatorChar + meetingId;
|
||||
|
@ -0,0 +1,44 @@
|
||||
/**
|
||||
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
|
||||
*
|
||||
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the
|
||||
* terms of the GNU Lesser General Public License as published by the Free Software
|
||||
* Foundation; either version 3.0 of the License, or (at your option) any later
|
||||
* version.
|
||||
*
|
||||
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License along
|
||||
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.bigbluebutton.api.domain;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class Extension {
|
||||
private String type;
|
||||
private Map<String,Object> properties;
|
||||
|
||||
public Extension(String type, Map<String,Object> properties) {
|
||||
this.type = type;
|
||||
this.properties = properties;
|
||||
}
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
public Map<String,Object> getProperties() {
|
||||
return properties;
|
||||
}
|
||||
public void setProperties(Map<String,Object> properties) {
|
||||
this.properties = properties;
|
||||
}
|
||||
}
|
@ -18,15 +18,16 @@
|
||||
*/
|
||||
|
||||
package org.bigbluebutton.api.domain;
|
||||
import groovy.util.slurpersupport.GPathResult;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class Playback {
|
||||
private String format;
|
||||
private String url;
|
||||
private int length;
|
||||
private GPathResult extensions;
|
||||
private List<Extension> extensions;
|
||||
|
||||
public Playback(String format, String url, int length, GPathResult extensions) {
|
||||
public Playback(String format, String url, int length, List<Extension> extensions) {
|
||||
this.format = format;
|
||||
this.url = url;
|
||||
this.length = length;
|
||||
@ -50,11 +51,10 @@ public class Playback {
|
||||
public void setLength(int length) {
|
||||
this.length = length;
|
||||
}
|
||||
public GPathResult getExtensions() {
|
||||
public List<Extension> getExtensions() {
|
||||
return extensions;
|
||||
}
|
||||
public void setExtensions(GPathResult extensions) {
|
||||
public void setExtensions(List<Extension> extensions) {
|
||||
this.extensions = extensions;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -27,8 +27,6 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import groovy.util.slurpersupport.GPathResult;
|
||||
|
||||
public class Recording {
|
||||
private String id;
|
||||
private String meetingID;
|
||||
@ -44,16 +42,16 @@ public class Recording {
|
||||
private String playbackLink;
|
||||
private String playbackFormat;
|
||||
private String playbackDuration;
|
||||
private GPathResult playbackExtensions;
|
||||
private List<Extension> playbackExtensions;
|
||||
|
||||
public static final String STATE_PROCESSING = "processing";
|
||||
public static final String STATE_PROCESSED = "processed";
|
||||
public static final String STATE_PUBLISING = "publishing";
|
||||
public static final String STATE_PUBLISHED = "published";
|
||||
public static final String STATE_UNPUBLISING = "unpublishing";
|
||||
public static final String STATE_UNPUBLISHED = "unpublished";
|
||||
public static final String STATE_DELETING = "deleting";
|
||||
public static final String STATE_DELETED = "deleted";
|
||||
public static final String STATE_PROCESSING = "processing";
|
||||
public static final String STATE_PROCESSED = "processed";
|
||||
public static final String STATE_PUBLISING = "publishing";
|
||||
public static final String STATE_PUBLISHED = "published";
|
||||
public static final String STATE_UNPUBLISING = "unpublishing";
|
||||
public static final String STATE_UNPUBLISHED = "unpublished";
|
||||
public static final String STATE_DELETING = "deleting";
|
||||
public static final String STATE_DELETED = "deleted";
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
@ -123,11 +121,11 @@ public class Recording {
|
||||
this.playbackDuration = playbackDuration;
|
||||
}
|
||||
|
||||
public GPathResult getPlaybackExtensions() {
|
||||
public List<Extension> getPlaybackExtensions() {
|
||||
return playbackExtensions;
|
||||
}
|
||||
|
||||
public void setPlaybackExtensions(GPathResult playbackExtensions) {
|
||||
public void setPlaybackExtensions(List<Extension> playbackExtensions) {
|
||||
this.playbackExtensions = playbackExtensions;
|
||||
}
|
||||
|
||||
|
@ -79,7 +79,8 @@ public class MeetingMessageHandler implements MessageHandler {
|
||||
msg.payload.viewerPassword,
|
||||
msg.payload.moderatorPassword,
|
||||
msg.payload.durationInMinutes,
|
||||
msg.payload.defaultPresentationURL,
|
||||
msg.payload.sourcePresentationId,
|
||||
msg.payload.sourcePresentationSlide,
|
||||
msg.payload.record
|
||||
)
|
||||
);
|
||||
|
@ -9,13 +9,15 @@ public class CreateBreakoutRoom implements IMessage {
|
||||
public final String viewerPassword;
|
||||
public final String moderatorPassword;
|
||||
public final Integer durationInMinutes; // The duration of the breakout room
|
||||
public final String defaultPresentationURL;
|
||||
public final String sourcePresentationId;
|
||||
public final Integer sourcePresentationSlide;
|
||||
public final Boolean record;
|
||||
|
||||
public CreateBreakoutRoom(String breakoutId, String parentId, String name,
|
||||
String voiceConfId, String viewerPassword,
|
||||
String moderatorPassword, Integer duration,
|
||||
String defaultPresentationURL, Boolean record) {
|
||||
String sourcePresentationId, Integer sourcePresentationSlide,
|
||||
Boolean record) {
|
||||
this.breakoutId = breakoutId;
|
||||
this.parentId = parentId;
|
||||
this.name = name;
|
||||
@ -23,7 +25,8 @@ public class CreateBreakoutRoom implements IMessage {
|
||||
this.viewerPassword = viewerPassword;
|
||||
this.moderatorPassword = moderatorPassword;
|
||||
this.durationInMinutes = duration;
|
||||
this.defaultPresentationURL = defaultPresentationURL;
|
||||
this.sourcePresentationId = sourcePresentationId;
|
||||
this.sourcePresentationSlide = sourcePresentationSlide;
|
||||
this.record = record;
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
package org.bigbluebutton.presentation;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FilenameFilter;
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.Timer;
|
||||
|
||||
import org.apache.commons.codec.digest.DigestUtils;
|
||||
import org.apache.commons.httpclient.HttpClient;
|
||||
@ -13,157 +13,211 @@ import org.apache.commons.httpclient.HttpException;
|
||||
import org.apache.commons.httpclient.HttpStatus;
|
||||
import org.apache.commons.httpclient.methods.GetMethod;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.bigbluebutton.api.Util;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class PresentationUrlDownloadService {
|
||||
private static Logger log = LoggerFactory.getLogger(PresentationUrlDownloadService.class);
|
||||
|
||||
private final int maxRedirects = 5;
|
||||
private DocumentConversionService documentConversionService;
|
||||
private String presentationBaseURL;
|
||||
private String presentationDir;
|
||||
|
||||
public void processUploadedPresentation(UploadedPresentation uploadedPres) {
|
||||
documentConversionService.processDocument(uploadedPres);
|
||||
}
|
||||
|
||||
public void processUploadedFile(String meetingId, String presId, String filename, File presFile) {
|
||||
UploadedPresentation uploadedPres = new UploadedPresentation(meetingId, presId, filename, presentationBaseURL);
|
||||
uploadedPres.setUploadedFile(presFile);
|
||||
processUploadedPresentation(uploadedPres);
|
||||
}
|
||||
|
||||
public void downloadAndProcessDocument(String address, String meetingId) {
|
||||
log.debug("downloadAndProcessDocument [pres=" + address + ", meeting=" + meetingId + "]");
|
||||
|
||||
String presFilename = address.substring(address.lastIndexOf('/') + 1);
|
||||
log.debug("downloadAndProcessDocument [filename=" + presFilename + "]");
|
||||
String filenameExt = getFilenameExt(presFilename);
|
||||
private static Logger log = LoggerFactory
|
||||
.getLogger(PresentationUrlDownloadService.class);
|
||||
|
||||
String presId = generatePresentationId(presFilename);
|
||||
File uploadDir = createPresentationDirectory(meetingId, presentationDir, presId);
|
||||
if (uploadDir != null) {
|
||||
String newFilename = createNewFilename(presId, filenameExt);
|
||||
String newFilePath = uploadDir.getAbsolutePath() + File.separatorChar + newFilename;
|
||||
|
||||
if (savePresentation(meetingId, newFilePath, address)) {
|
||||
File pres = new File(newFilePath);
|
||||
processUploadedFile(meetingId, presId, presFilename, pres);
|
||||
} else {
|
||||
log.error("Failed to download presentation=[" + address + "], meeting=[" + meetingId + "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String generatePresentationId(String name) {
|
||||
long timestamp = System.currentTimeMillis();
|
||||
return DigestUtils.shaHex(name) + "-" + timestamp;
|
||||
}
|
||||
|
||||
public String getFilenameExt(String filename) {
|
||||
return filename.substring(filename.lastIndexOf("."));
|
||||
}
|
||||
|
||||
public String createNewFilename(String presId, String fileExt) {
|
||||
return presId + fileExt;
|
||||
}
|
||||
|
||||
public File createPresentationDirectory(String meetingId, String presentationDir, String presentationId) {
|
||||
String meetingPath = presentationDir + File.separatorChar + meetingId + File.separatorChar + meetingId;
|
||||
String presPath = meetingPath + File.separatorChar + presentationId;
|
||||
File dir = new File(presPath);
|
||||
log.debug("Creating dir [{}]", presPath);
|
||||
if (dir.mkdirs()) {
|
||||
return dir;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String followRedirect(String meetingId, String redirectUrl,
|
||||
int redirectCount, String origUrl) {
|
||||
private final int maxRedirects = 5;
|
||||
private PageExtractor pageExtractor;
|
||||
private DocumentConversionService documentConversionService;
|
||||
private String presentationBaseURL;
|
||||
private String presentationDir;
|
||||
|
||||
if (redirectCount > maxRedirects) {
|
||||
log.error("Max redirect reached for meeting=[{}] with url=[{}]", meetingId, origUrl);
|
||||
return null;
|
||||
}
|
||||
|
||||
URL presUrl;
|
||||
try {
|
||||
presUrl = new URL(redirectUrl);
|
||||
} catch (MalformedURLException e) {
|
||||
log.error("Malformed url=[{}] for meeting=[{}]", redirectUrl, meetingId);
|
||||
return null;
|
||||
}
|
||||
|
||||
HttpURLConnection conn;
|
||||
try {
|
||||
conn = (HttpURLConnection) presUrl.openConnection();
|
||||
conn.setReadTimeout(5000);
|
||||
conn.addRequestProperty("Accept-Language", "en-US,en;q=0.8");
|
||||
conn.addRequestProperty("User-Agent", "Mozilla");
|
||||
|
||||
// normally, 3xx is redirect
|
||||
int status = conn.getResponseCode();
|
||||
if (status != HttpURLConnection.HTTP_OK) {
|
||||
if (status == HttpURLConnection.HTTP_MOVED_TEMP
|
||||
|| status == HttpURLConnection.HTTP_MOVED_PERM
|
||||
|| status == HttpURLConnection.HTTP_SEE_OTHER) {
|
||||
String newUrl = conn.getHeaderField("Location");
|
||||
return followRedirect(meetingId, newUrl, redirectCount + 1, origUrl);
|
||||
} else {
|
||||
log.error("Invalid HTTP response=[{}] for url=[{}] with meeting[{}]", status, redirectUrl, meetingId);
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return redirectUrl;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("IOException for url=[{}] with meeting[{}]", redirectUrl, meetingId);
|
||||
return null;
|
||||
public void processUploadedPresentation(UploadedPresentation uploadedPres) {
|
||||
documentConversionService.processDocument(uploadedPres);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean savePresentation(final String meetingId, final String filename,
|
||||
final String urlString) {
|
||||
|
||||
String finalUrl = followRedirect(meetingId, urlString, 0, urlString);
|
||||
|
||||
if (finalUrl == null) return false;
|
||||
|
||||
boolean success = false;
|
||||
GetMethod method = new GetMethod(finalUrl);
|
||||
HttpClient client = new HttpClient();
|
||||
try {
|
||||
int statusCode = client.executeMethod(method);
|
||||
if (statusCode == HttpStatus.SC_OK) {
|
||||
FileUtils.copyInputStreamToFile(
|
||||
method.getResponseBodyAsStream(), new File(filename));
|
||||
log.info("Downloaded presentation at [{}]", finalUrl);
|
||||
success = true;
|
||||
}
|
||||
} catch (HttpException e) {
|
||||
log.error("HttpException while downloading presentation at [{}]", finalUrl);
|
||||
} catch (IOException e) {
|
||||
log.error("IOException while downloading presentation at [{}]", finalUrl);
|
||||
} finally {
|
||||
method.releaseConnection();
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
public void setPresentationDir(String presDir) {
|
||||
presentationDir = presDir;
|
||||
}
|
||||
|
||||
public void setPresentationBaseURL(String presentationBaseUrl) {
|
||||
presentationBaseURL = presentationBaseUrl;
|
||||
}
|
||||
|
||||
public void setDocumentConversionService(DocumentConversionService documentConversionService) {
|
||||
this.documentConversionService = documentConversionService;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void processUploadedFile(String meetingId, String presId,
|
||||
String filename, File presFile) {
|
||||
UploadedPresentation uploadedPres = new UploadedPresentation(meetingId,
|
||||
presId, filename, presentationBaseURL);
|
||||
uploadedPres.setUploadedFile(presFile);
|
||||
processUploadedPresentation(uploadedPres);
|
||||
}
|
||||
|
||||
public void extractPage(String sourceMeetingId, String presentationId,
|
||||
Integer presentationSlide, String destinationMeetingId) {
|
||||
|
||||
// Construct the source meeting path
|
||||
File sourceMeetingPath = new File(presentationDir + File.separator
|
||||
+ sourceMeetingId + File.separator + sourceMeetingId
|
||||
+ File.separator + presentationId);
|
||||
|
||||
final String presentationFilter = presentationId;
|
||||
FilenameFilter pdfFilter = new FilenameFilter() {
|
||||
public boolean accept(File dir, String name) {
|
||||
return name.startsWith(presentationFilter)
|
||||
&& name.toLowerCase().endsWith("pdf");
|
||||
}
|
||||
};
|
||||
|
||||
File[] matches = sourceMeetingPath.listFiles(pdfFilter);
|
||||
if (matches.length != 1) {
|
||||
// No PDF presentation was found, we look for an image presentation
|
||||
FilenameFilter imgFlter = new FilenameFilter() {
|
||||
public boolean accept(File dir, String name) {
|
||||
return name.startsWith(presentationFilter);
|
||||
}
|
||||
};
|
||||
|
||||
matches = sourceMeetingPath.listFiles(imgFlter);
|
||||
}
|
||||
if (matches.length != 1) {
|
||||
log.info("Not matching PDF file with prefix {} found at {}",
|
||||
sourceMeetingId, sourceMeetingPath);
|
||||
} else {
|
||||
File sourcePresentationFile = matches[0];
|
||||
String filenameExt = FilenameUtils
|
||||
.getExtension(sourcePresentationFile.getName());
|
||||
String presId = generatePresentationId(presentationId);
|
||||
String newFilename = Util.createNewFilename(presId, filenameExt);
|
||||
|
||||
File uploadDir = createPresentationDirectory(destinationMeetingId,
|
||||
presentationDir, presId);
|
||||
String newFilePath = uploadDir.getAbsolutePath()
|
||||
+ File.separatorChar + newFilename;
|
||||
File newPresentation = new File(newFilePath);
|
||||
|
||||
if (sourcePresentationFile.getName().toLowerCase().endsWith("pdf")) {
|
||||
pageExtractor.extractPage(sourcePresentationFile, new File(
|
||||
newFilePath), presentationSlide);
|
||||
} else {
|
||||
try {
|
||||
FileUtils.copyFile(sourcePresentationFile, newPresentation);
|
||||
} catch (IOException e) {
|
||||
log.error("Could not copy presentation {} to {}",
|
||||
sourcePresentationFile.getAbsolutePath(),
|
||||
newPresentation.getAbsolutePath());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
processUploadedFile(destinationMeetingId, presId, "default-"
|
||||
+ presentationSlide.toString() + "." + filenameExt,
|
||||
newPresentation);
|
||||
}
|
||||
}
|
||||
|
||||
public String generatePresentationId(String name) {
|
||||
long timestamp = System.currentTimeMillis();
|
||||
return DigestUtils.shaHex(name) + "-" + timestamp;
|
||||
}
|
||||
|
||||
public File createPresentationDirectory(String meetingId,
|
||||
String presentationDir, String presentationId) {
|
||||
String meetingPath = presentationDir + File.separatorChar + meetingId
|
||||
+ File.separatorChar + meetingId;
|
||||
String presPath = meetingPath + File.separatorChar + presentationId;
|
||||
File dir = new File(presPath);
|
||||
log.debug("Creating dir [{}]", presPath);
|
||||
if (dir.mkdirs()) {
|
||||
return dir;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String followRedirect(String meetingId, String redirectUrl,
|
||||
int redirectCount, String origUrl) {
|
||||
|
||||
if (redirectCount > maxRedirects) {
|
||||
log.error("Max redirect reached for meeting=[{}] with url=[{}]",
|
||||
meetingId, origUrl);
|
||||
return null;
|
||||
}
|
||||
|
||||
URL presUrl;
|
||||
try {
|
||||
presUrl = new URL(redirectUrl);
|
||||
} catch (MalformedURLException e) {
|
||||
log.error("Malformed url=[{}] for meeting=[{}]", redirectUrl,
|
||||
meetingId);
|
||||
return null;
|
||||
}
|
||||
|
||||
HttpURLConnection conn;
|
||||
try {
|
||||
conn = (HttpURLConnection) presUrl.openConnection();
|
||||
conn.setReadTimeout(5000);
|
||||
conn.addRequestProperty("Accept-Language", "en-US,en;q=0.8");
|
||||
conn.addRequestProperty("User-Agent", "Mozilla");
|
||||
|
||||
// normally, 3xx is redirect
|
||||
int status = conn.getResponseCode();
|
||||
if (status != HttpURLConnection.HTTP_OK) {
|
||||
if (status == HttpURLConnection.HTTP_MOVED_TEMP
|
||||
|| status == HttpURLConnection.HTTP_MOVED_PERM
|
||||
|| status == HttpURLConnection.HTTP_SEE_OTHER) {
|
||||
String newUrl = conn.getHeaderField("Location");
|
||||
return followRedirect(meetingId, newUrl, redirectCount + 1,
|
||||
origUrl);
|
||||
} else {
|
||||
log.error(
|
||||
"Invalid HTTP response=[{}] for url=[{}] with meeting[{}]",
|
||||
status, redirectUrl, meetingId);
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return redirectUrl;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("IOException for url=[{}] with meeting[{}]", redirectUrl,
|
||||
meetingId);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean savePresentation(final String meetingId,
|
||||
final String filename, final String urlString) {
|
||||
|
||||
String finalUrl = followRedirect(meetingId, urlString, 0, urlString);
|
||||
|
||||
if (finalUrl == null)
|
||||
return false;
|
||||
|
||||
boolean success = false;
|
||||
GetMethod method = new GetMethod(finalUrl);
|
||||
HttpClient client = new HttpClient();
|
||||
try {
|
||||
int statusCode = client.executeMethod(method);
|
||||
if (statusCode == HttpStatus.SC_OK) {
|
||||
FileUtils.copyInputStreamToFile(
|
||||
method.getResponseBodyAsStream(), new File(filename));
|
||||
log.info("Downloaded presentation at [{}]", finalUrl);
|
||||
success = true;
|
||||
}
|
||||
} catch (HttpException e) {
|
||||
log.error("HttpException while downloading presentation at [{}]",
|
||||
finalUrl);
|
||||
} catch (IOException e) {
|
||||
log.error("IOException while downloading presentation at [{}]",
|
||||
finalUrl);
|
||||
} finally {
|
||||
method.releaseConnection();
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
public void setPageExtractor(PageExtractor extractor) {
|
||||
this.pageExtractor = extractor;
|
||||
}
|
||||
|
||||
public void setPresentationDir(String presDir) {
|
||||
presentationDir = presDir;
|
||||
}
|
||||
|
||||
public void setPresentationBaseURL(String presentationBaseUrl) {
|
||||
presentationBaseURL = presentationBaseUrl;
|
||||
}
|
||||
|
||||
public void setDocumentConversionService(
|
||||
DocumentConversionService documentConversionService) {
|
||||
this.documentConversionService = documentConversionService;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -21,11 +21,13 @@ package org.bigbluebutton.presentation;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.bigbluebutton.api.messaging.MessagingConstants;
|
||||
import org.bigbluebutton.api.messaging.MessagingService;
|
||||
import org.bigbluebutton.presentation.ConversionUpdateMessage.MessageBuilder;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
public class SupportedDocumentFilter {
|
||||
@ -41,13 +43,15 @@ public class SupportedDocumentFilter {
|
||||
File presentationFile = pres.getUploadedFile();
|
||||
|
||||
/* Get file extension - Perhaps try to rely on a more accurate method than an extension type ? */
|
||||
int fileExtIndex = presentationFile.getName().lastIndexOf('.') + 1;
|
||||
String ext = presentationFile.getName().toLowerCase().substring(fileExtIndex);
|
||||
boolean supported = SupportedFileTypes.isFileSupported(ext);
|
||||
String extension = FilenameUtils.getExtension(presentationFile.getName());
|
||||
boolean supported = SupportedFileTypes.isFileSupported(extension);
|
||||
notifyProgressListener(supported, pres);
|
||||
if (supported) {
|
||||
log.info("Received supported file " + pres.getUploadedFile().getAbsolutePath());
|
||||
pres.setFileType(ext);
|
||||
log.info("Received supported file {}", pres.getUploadedFile().getAbsolutePath());
|
||||
pres.setFileType(extension);
|
||||
}
|
||||
else {
|
||||
log.warn("Received not supported file {}", pres.getUploadedFile().getAbsolutePath());
|
||||
}
|
||||
return supported;
|
||||
}
|
||||
@ -65,9 +69,9 @@ public class SupportedDocumentFilter {
|
||||
Gson gson= new Gson();
|
||||
String updateMsg=gson.toJson(builder.build().getMessage());
|
||||
log.debug("sending: "+updateMsg);
|
||||
messagingService.send(MessagingConstants.TO_PRESENTATION_CHANNEL, updateMsg);
|
||||
messagingService.send(MessagingConstants.TO_PRESENTATION_CHANNEL, updateMsg);
|
||||
} else {
|
||||
log.warn("MessagingService has not been set!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,56 +1,60 @@
|
||||
/**
|
||||
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
|
||||
*
|
||||
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the
|
||||
* terms of the GNU Lesser General Public License as published by the Free Software
|
||||
* Foundation; either version 3.0 of the License, or (at your option) any later
|
||||
* version.
|
||||
*
|
||||
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License along
|
||||
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
|
||||
*
|
||||
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the
|
||||
* terms of the GNU Lesser General Public License as published by the Free Software
|
||||
* Foundation; either version 3.0 of the License, or (at your option) any later
|
||||
* version.
|
||||
*
|
||||
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License along
|
||||
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.bigbluebutton.presentation.imp;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.bigbluebutton.presentation.PageExtractor;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class GhostscriptPageExtractor implements PageExtractor {
|
||||
private static Logger log = LoggerFactory.getLogger(GhostscriptPageExtractor.class);
|
||||
|
||||
private String GHOSTSCRIPT_EXEC;
|
||||
private String noPdfMarkWorkaround;
|
||||
private String SPACE = " ";
|
||||
|
||||
public boolean extractPage(File presentationFile, File output, int page){
|
||||
String OPTIONS = "-sDEVICE=pdfwrite -dNOPAUSE -dQUIET -dBATCH";
|
||||
String FIRST_PAGE = "-dFirstPage=" + page;
|
||||
String LAST_PAGE = "-dLastPage=" + page;
|
||||
String DESTINATION = output.getAbsolutePath();
|
||||
String OUTPUT_FILE = "-sOutputFile=" + DESTINATION;
|
||||
|
||||
//extract that specific page and create a temp-pdf(only one page) with GhostScript
|
||||
String COMMAND = GHOSTSCRIPT_EXEC + SPACE + OPTIONS + SPACE + FIRST_PAGE + SPACE + LAST_PAGE + SPACE
|
||||
+ OUTPUT_FILE + SPACE + noPdfMarkWorkaround + SPACE + presentationFile.getAbsolutePath();
|
||||
|
||||
log.debug(COMMAND);
|
||||
private static Logger log = LoggerFactory
|
||||
.getLogger(GhostscriptPageExtractor.class);
|
||||
|
||||
private String GHOSTSCRIPT_EXEC;
|
||||
private String noPdfMarkWorkaround;
|
||||
private String SPACE = " ";
|
||||
|
||||
public boolean extractPage(File presentationFile, File output, int page) {
|
||||
String OPTIONS = "-sDEVICE=pdfwrite -dNOPAUSE -dQUIET -dBATCH";
|
||||
String FIRST_PAGE = "-dFirstPage=" + page;
|
||||
String LAST_PAGE = "-dLastPage=" + page;
|
||||
String DESTINATION = output.getAbsolutePath();
|
||||
String OUTPUT_FILE = "-sOutputFile=" + DESTINATION;
|
||||
|
||||
// extract that specific page and create a temp-pdf(only one page) with
|
||||
// GhostScript
|
||||
String COMMAND = GHOSTSCRIPT_EXEC + SPACE + OPTIONS + SPACE
|
||||
+ FIRST_PAGE + SPACE + LAST_PAGE + SPACE + OUTPUT_FILE + SPACE
|
||||
+ noPdfMarkWorkaround + SPACE
|
||||
+ presentationFile.getAbsolutePath();
|
||||
|
||||
return new ExternalProcessExecutor().exec(COMMAND, 60000);
|
||||
}
|
||||
|
||||
public void setGhostscriptExec(String exec) {
|
||||
GHOSTSCRIPT_EXEC = exec;
|
||||
}
|
||||
|
||||
public void setNoPdfMarkWorkaround(String noPdfMarkWorkaround) {
|
||||
this.noPdfMarkWorkaround = noPdfMarkWorkaround;
|
||||
}
|
||||
}
|
||||
|
||||
public void setGhostscriptExec(String exec) {
|
||||
GHOSTSCRIPT_EXEC = exec;
|
||||
}
|
||||
|
||||
public void setNoPdfMarkWorkaround(String noPdfMarkWorkaround) {
|
||||
this.noPdfMarkWorkaround = noPdfMarkWorkaround;
|
||||
}
|
||||
}
|
||||
|
@ -13,93 +13,113 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class SvgImageCreatorImp implements SvgImageCreator {
|
||||
private static Logger log = LoggerFactory.getLogger(SvgImageCreatorImp.class);
|
||||
|
||||
private String IMAGEMAGICK_DIR;
|
||||
|
||||
@Override
|
||||
public boolean createSvgImages(UploadedPresentation pres) {
|
||||
boolean success = false;
|
||||
File imagePresentationDir = determineSvgImagesDirectory(pres.getUploadedFile());
|
||||
if (! imagePresentationDir.exists())
|
||||
imagePresentationDir.mkdir();
|
||||
|
||||
cleanDirectory(imagePresentationDir);
|
||||
|
||||
try {
|
||||
extractPdfPages(pres);
|
||||
success = generateSvgImages(imagePresentationDir,pres);
|
||||
} catch (InterruptedException e) {
|
||||
log.warn("Interrupted Exception while generating images.");
|
||||
success = false;
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
private void extractPdfPages(UploadedPresentation pres){
|
||||
File tmpDir = new File(pres.getUploadedFile().getParent() + File.separatorChar + "svgs" + File.separatorChar + "tmp");
|
||||
if (! tmpDir.exists())
|
||||
tmpDir.mkdir();
|
||||
|
||||
if(SupportedFileTypes.isPdfFile(pres.getFileType())){
|
||||
for(int i=1; i<=pres.getNumberOfPages(); i++){
|
||||
File tmp = new File(tmpDir.getAbsolutePath() + File.separatorChar + "slide" + i + ".pdf");
|
||||
String COMMAND = IMAGEMAGICK_DIR + "/gs -sDEVICE=pdfwrite -dNOPAUSE -dQUIET -dBATCH -dFirstPage=" + i + " -dLastPage=" + i + " -sOutputFile=" + tmp.getAbsolutePath() + " /etc/bigbluebutton/nopdfmark.ps " + pres.getUploadedFile().getAbsolutePath();
|
||||
new ExternalProcessExecutor().exec(COMMAND, 60000);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
private static Logger log = LoggerFactory
|
||||
.getLogger(SvgImageCreatorImp.class);
|
||||
|
||||
private boolean generateSvgImages(File imagePresentationDir, UploadedPresentation pres) throws InterruptedException {
|
||||
String source = pres.getUploadedFile().getAbsolutePath();
|
||||
String dest;
|
||||
String COMMAND = "";
|
||||
boolean done = true;
|
||||
if(SupportedFileTypes.isImageFile(pres.getFileType())){
|
||||
dest = imagePresentationDir.getAbsolutePath() + File.separator + "slide1.pdf";
|
||||
COMMAND = IMAGEMAGICK_DIR + "/convert " + source + " " + dest;
|
||||
done = new ExternalProcessExecutor().exec(COMMAND, 60000);
|
||||
private String IMAGEMAGICK_DIR;
|
||||
|
||||
source = imagePresentationDir.getAbsolutePath() + File.separator + "slide1.pdf";
|
||||
dest = imagePresentationDir.getAbsolutePath() + File.separator + "slide1.svg";
|
||||
COMMAND = "pdftocairo -rx 300 -ry 300 -svg -q -f 1 -l 1 " + source + " " + dest;
|
||||
done = new ExternalProcessExecutor().exec(COMMAND, 60000);
|
||||
|
||||
}else{
|
||||
for(int i=1; i<=pres.getNumberOfPages(); i++){
|
||||
File tmp = new File(imagePresentationDir.getAbsolutePath() + File.separatorChar + "tmp" + File.separatorChar + "slide" + i + ".pdf");
|
||||
File destsvg = new File(imagePresentationDir.getAbsolutePath() + File.separatorChar + "slide" + i + ".svg");
|
||||
COMMAND = "pdftocairo -rx 300 -ry 300 -svg -q -f 1 -l 1 " + File.separatorChar + tmp.getAbsolutePath() + " " + destsvg.getAbsolutePath();
|
||||
@Override
|
||||
public boolean createSvgImages(UploadedPresentation pres) {
|
||||
boolean success = false;
|
||||
File svgImagesPresentationDir = determineSvgImagesDirectory(pres
|
||||
.getUploadedFile());
|
||||
if (!svgImagesPresentationDir.exists())
|
||||
svgImagesPresentationDir.mkdir();
|
||||
|
||||
done = new ExternalProcessExecutor().exec(COMMAND, 60000);
|
||||
if(!done){
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (done) {
|
||||
return true;
|
||||
}
|
||||
log.warn("Failed to create svg images: " + COMMAND);
|
||||
return false;
|
||||
}
|
||||
|
||||
private File determineSvgImagesDirectory(File presentationFile) {
|
||||
return new File(presentationFile.getParent() + File.separatorChar + "svgs");
|
||||
}
|
||||
|
||||
private void cleanDirectory(File directory) {
|
||||
File[] files = directory.listFiles();
|
||||
for (int i = 0; i < files.length; i++) {
|
||||
files[i].delete();
|
||||
}
|
||||
}
|
||||
cleanDirectory(svgImagesPresentationDir);
|
||||
|
||||
public void setImageMagickDir(String imageMagickDir) {
|
||||
IMAGEMAGICK_DIR = imageMagickDir;
|
||||
}
|
||||
try {
|
||||
extractPdfPages(pres);
|
||||
success = generateSvgImages(svgImagesPresentationDir, pres);
|
||||
} catch (InterruptedException e) {
|
||||
log.warn("Interrupted Exception while generating images.");
|
||||
success = false;
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
private void extractPdfPages(UploadedPresentation pres) {
|
||||
File pdfDir = new File(pres.getUploadedFile().getParent()
|
||||
+ File.separatorChar + "pdfs");
|
||||
if (!pdfDir.exists())
|
||||
pdfDir.mkdir();
|
||||
|
||||
if (SupportedFileTypes.isPdfFile(pres.getFileType())) {
|
||||
for (int i = 1; i <= pres.getNumberOfPages(); i++) {
|
||||
File pdfFile = new File(pdfDir.getAbsolutePath()
|
||||
+ File.separatorChar + "slide" + i + ".pdf");
|
||||
String COMMAND = IMAGEMAGICK_DIR
|
||||
+ "/gs -sDEVICE=pdfwrite -dNOPAUSE -dQUIET -dBATCH -dFirstPage="
|
||||
+ i + " -dLastPage=" + i + " -sOutputFile="
|
||||
+ pdfFile.getAbsolutePath()
|
||||
+ " /etc/bigbluebutton/nopdfmark.ps "
|
||||
+ pres.getUploadedFile().getAbsolutePath();
|
||||
new ExternalProcessExecutor().exec(COMMAND, 60000);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private boolean generateSvgImages(File imagePresentationDir,
|
||||
UploadedPresentation pres) throws InterruptedException {
|
||||
String source = pres.getUploadedFile().getAbsolutePath();
|
||||
String dest;
|
||||
String COMMAND = "";
|
||||
boolean done = true;
|
||||
if (SupportedFileTypes.isImageFile(pres.getFileType())) {
|
||||
dest = imagePresentationDir.getAbsolutePath() + File.separator
|
||||
+ "slide1.pdf";
|
||||
COMMAND = IMAGEMAGICK_DIR + "/convert " + source + " " + dest;
|
||||
done = new ExternalProcessExecutor().exec(COMMAND, 60000);
|
||||
|
||||
source = imagePresentationDir.getAbsolutePath() + File.separator
|
||||
+ "slide1.pdf";
|
||||
dest = imagePresentationDir.getAbsolutePath() + File.separator
|
||||
+ "slide1.svg";
|
||||
COMMAND = "pdftocairo -rx 300 -ry 300 -svg -q -f 1 -l 1 " + source
|
||||
+ " " + dest;
|
||||
done = new ExternalProcessExecutor().exec(COMMAND, 60000);
|
||||
|
||||
} else {
|
||||
for (int i = 1; i <= pres.getNumberOfPages(); i++) {
|
||||
File pdfFile = new File(imagePresentationDir.getParent()
|
||||
+ File.separatorChar + "pdfs" + File.separatorChar
|
||||
+ "slide" + i + ".pdf");
|
||||
File destsvg = new File(imagePresentationDir.getAbsolutePath()
|
||||
+ File.separatorChar + "slide" + i + ".svg");
|
||||
COMMAND = "pdftocairo -rx 300 -ry 300 -svg -q -f 1 -l 1 "
|
||||
+ File.separatorChar + pdfFile.getAbsolutePath() + " "
|
||||
+ destsvg.getAbsolutePath();
|
||||
|
||||
done = new ExternalProcessExecutor().exec(COMMAND, 60000);
|
||||
if (!done) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (done) {
|
||||
return true;
|
||||
}
|
||||
log.warn("Failed to create svg images: " + COMMAND);
|
||||
return false;
|
||||
}
|
||||
|
||||
private File determineSvgImagesDirectory(File presentationFile) {
|
||||
return new File(presentationFile.getParent() + File.separatorChar
|
||||
+ "svgs");
|
||||
}
|
||||
|
||||
private void cleanDirectory(File directory) {
|
||||
File[] files = directory.listFiles();
|
||||
for (int i = 0; i < files.length; i++) {
|
||||
files[i].delete();
|
||||
}
|
||||
}
|
||||
|
||||
public void setImageMagickDir(String imageMagickDir) {
|
||||
IMAGEMAGICK_DIR = imageMagickDir;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -28,7 +28,31 @@
|
||||
<type>${p.getFormat()}</type>
|
||||
<url>${p.getUrl()}</url>
|
||||
<length>${p.getLength()}</length>
|
||||
<#-- Missing p.getExtensions() -->
|
||||
<#if p.getExtensions()??>
|
||||
<#list p.getExtensions() as extension>
|
||||
<${extension.getType()}>
|
||||
<#assign properties = extension.getProperties()>
|
||||
<#if extension.getType() == "preview">
|
||||
<#list properties?keys as property>
|
||||
<#if property == "images">
|
||||
<${property}>
|
||||
<#if properties[property]["image"]?? && properties[property]["image"]?is_hash>
|
||||
<#assign image = properties[property]["image"]>
|
||||
<image <#if image["attributes"]?? && image["attributes"]["width"]??>width="${image["attributes"]["width"]}"</#if> <#if image["attributes"]?? && image["attributes"]["height"]??>height="${image["attributes"]["height"]}"</#if> <#if image["attributes"]?? && image["attributes"]["alt"]??>alt="${image["attributes"]["alt"]}"</#if>>${image["text"]}</image>
|
||||
<#elseif properties[property]["image"]?is_enumerable>
|
||||
<#list properties[property]["image"] as image>
|
||||
<image <#if image["attributes"]?? && image["attributes"]["width"]??>width="${image["attributes"]["width"]}"</#if> <#if image["attributes"]?? && image["attributes"]["height"]??>height="${image["attributes"]["height"]}"</#if> <#if image["attributes"]?? && image["attributes"]["alt"]??>alt="${image["attributes"]["alt"]}"</#if>>${image["text"]}</image>
|
||||
</#list>
|
||||
</#if>
|
||||
</${property}>
|
||||
<#else>
|
||||
<${property} />
|
||||
</#if>
|
||||
</#list>
|
||||
</#if>
|
||||
</${extension.getType()}>
|
||||
</#list>
|
||||
</#if>
|
||||
</format>
|
||||
</#if>
|
||||
</#list>
|
||||
|
@ -67,12 +67,51 @@ module BigBlueButton
|
||||
self.extract_png_page_from_pdf(1, pdf_page, png_out, '800x600')
|
||||
end
|
||||
|
||||
#Convert an image to a png
|
||||
# Convert an image to a png
|
||||
def self.convert_image_to_png(image, png_image, resize = '800x600')
|
||||
BigBlueButton.logger.info("Task: Converting image to .png")
|
||||
command = "convert #{image} -resize #{resize} -background white -flatten #{png_image}"
|
||||
BigBlueButton.execute(command)
|
||||
BigBlueButton.logger.info("Task: Converting image to .png")
|
||||
command = "convert #{image} -resize #{resize} -background white -flatten #{png_image}"
|
||||
BigBlueButton.execute(command)
|
||||
end
|
||||
|
||||
# Gathers the text from the slide
|
||||
def self.get_text_from_slide(textfiles_dir, slide_num)
|
||||
text_from_slide = nil
|
||||
if File.file?("#{textfiles_dir}/slide-#{slide_num}.txt")
|
||||
text_from_slide = File.open("#{textfiles_dir}/slide-#{slide_num}.txt") {|f| f.readline}
|
||||
unless text_from_slide == nil
|
||||
text_from_slide = text_from_slide.strip
|
||||
end
|
||||
end
|
||||
text_from_slide
|
||||
end
|
||||
|
||||
# Get from events the presentation that will be used for preview.
|
||||
def self.get_presentation_for_preview(process_dir)
|
||||
events_xml = "#{process_dir}/events.xml"
|
||||
BigBlueButton.logger.info("Task: Getting from events the presentation to be used for preview")
|
||||
presentation = {}
|
||||
doc = Nokogiri::XML(File.open(events_xml))
|
||||
doc.xpath("//event[@eventname='SharePresentationEvent']").each do |presentation_event|
|
||||
# Extract presentation data from events
|
||||
presentation_id = presentation_event.xpath("presentationName").text
|
||||
presentation_filename = presentation_event.xpath("originalFilename").text
|
||||
# Set textfile directory
|
||||
textfiles_dir = "#{process_dir}/presentation/#{presentation_id}/textfiles"
|
||||
# Set presentation hashmap to be returned
|
||||
presentation[:id] = presentation_id
|
||||
presentation[:filename] = presentation_filename
|
||||
presentation[:slides] = {}
|
||||
presentation[:slides][1] = { :alt => self.get_text_from_slide(textfiles_dir, 1) }
|
||||
unless presentation_filename == "default.pdf"
|
||||
presentation[:slides][2] = { :alt => self.get_text_from_slide(textfiles_dir, 2) } if File.file?("#{textfiles_dir}/slide-2.txt")
|
||||
presentation[:slides][3] = { :alt => self.get_text_from_slide(textfiles_dir, 3) } if File.file?("#{textfiles_dir}/slide-3.txt")
|
||||
# Break because something else than default.pdf was found
|
||||
break
|
||||
end
|
||||
end
|
||||
presentation
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
@ -165,6 +165,9 @@ if not FileTest.directory?(target_dir)
|
||||
command="convert #{images[0]} -resize 1600x1200 -background white -flatten #{target_pres_dir}/slide-1.png"
|
||||
BigBlueButton.execute(command)
|
||||
end
|
||||
|
||||
# Copy thumbnails from raw files
|
||||
FileUtils.cp_r("#{pres_dir}/thumbnails", "#{target_pres_dir}/thumbnails")
|
||||
end
|
||||
|
||||
BigBlueButton.logger.info("Generating closed captions")
|
||||
|
@ -1022,12 +1022,25 @@ begin
|
||||
playback.remove
|
||||
end
|
||||
## Add the actual playback
|
||||
presentation = BigBlueButton::Presentation.get_presentation_for_preview("#{$process_dir}")
|
||||
metadata_with_playback = Nokogiri::XML::Builder.with(metadata.at('recording')) do |xml|
|
||||
xml.playback {
|
||||
xml.format("presentation")
|
||||
xml.link("#{playback_protocol}://#{playback_host}/playback/presentation/0.9.0/playback.html?meetingId=#{$meeting_id}")
|
||||
xml.processing_time("#{processing_time}")
|
||||
xml.duration("#{recording_time}")
|
||||
unless presentation.empty?
|
||||
xml.extensions {
|
||||
xml.preview {
|
||||
xml.images {
|
||||
presentation[:slides].each do |key,val|
|
||||
attributes = {:width => "176", :height => "136", :alt => (val[:alt] != nil)? "#{val[:alt]}": ""}
|
||||
xml.image(attributes){ xml.text("#{playback_protocol}://#{playback_host}/presentation/#{$meeting_id}/presentation/#{presentation[:id]}/thumbnails/thumb-#{key}.png") }
|
||||
end
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
}
|
||||
end
|
||||
## Write the new metadata.xml
|
||||
|
Loading…
Reference in New Issue
Block a user