/** * BigBlueButton open source conferencing system - http://www.bigbluebutton.org/ * * Copyright (c) 2015 BigBlueButton Inc. and by respective authors (see below). * * This program is free software; you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License as published by the Free Software * Foundation; either version 3.0 of the License, or (at your option) any later * version. * * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License along * with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. * */ package org.bigbluebutton.presentation.imp; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.CompletionService; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorCompletionService; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import org.bigbluebutton.presentation.ConversionMessageConstants; import org.bigbluebutton.presentation.ConversionUpdateMessage; import org.bigbluebutton.presentation.ConversionUpdateMessage.MessageBuilder; import org.bigbluebutton.presentation.PageConverter; import org.bigbluebutton.presentation.PdfToSwfSlide; import org.bigbluebutton.presentation.SvgImageCreator; import org.bigbluebutton.presentation.TextFileCreator; import org.bigbluebutton.presentation.ThumbnailCreator; import org.bigbluebutton.presentation.UploadedPresentation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.gson.Gson; public class PdfToSwfSlidesGenerationService { private static Logger log = LoggerFactory .getLogger(PdfToSwfSlidesGenerationService.class); private SwfSlidesGenerationProgressNotifier notifier; private PageCounterService counterService; private PageConverter pdfToSwfConverter; private ExecutorService executor; private ThumbnailCreator thumbnailCreator; private TextFileCreator textFileCreator; private SvgImageCreator svgImageCreator; private long MAX_CONVERSION_TIME = 5 * 60 * 1000; private String BLANK_SLIDE; private int MAX_SWF_FILE_SIZE; private boolean svgImagesRequired; private final long CONVERSION_TIMEOUT = 20000000000L; // 20s public PdfToSwfSlidesGenerationService(int numConversionThreads) { executor = Executors.newFixedThreadPool(numConversionThreads); } public void generateSlides(UploadedPresentation pres) { determineNumberOfPages(pres); if (pres.getNumberOfPages() > 0) { convertPdfToSwf(pres); createTextFiles(pres); createThumbnails(pres); // only create SVG images if the configuration requires it if (svgImagesRequired) { createSvgImages(pres); } notifier.sendConversionCompletedMessage(pres); } } private boolean determineNumberOfPages(UploadedPresentation pres) { try { counterService.determineNumberOfPages(pres); return true; } catch (CountingPageException e) { sendFailedToCountPageMessage(e, pres); } return false; } private void sendFailedToCountPageMessage(CountingPageException e, UploadedPresentation pres) { MessageBuilder builder = new ConversionUpdateMessage.MessageBuilder(pres); if (e .getExceptionType() == CountingPageException.ExceptionType.PAGE_COUNT_EXCEPTION) { builder.messageKey(ConversionMessageConstants.PAGE_COUNT_FAILED_KEY); } else if (e .getExceptionType() == CountingPageException.ExceptionType.PAGE_EXCEEDED_EXCEPTION) { builder.numberOfPages(e.getPageCount()); builder.maxNumberPages(e.getMaxNumberOfPages()); builder.messageKey(ConversionMessageConstants.PAGE_COUNT_EXCEEDED_KEY); } notifier.sendConversionUpdateMessage(builder.build().getMessage()); } private void createThumbnails(UploadedPresentation pres) { notifier.sendCreatingThumbnailsUpdateMessage(pres); thumbnailCreator.createThumbnails(pres); } private void createTextFiles(UploadedPresentation pres) { notifier.sendCreatingTextFilesUpdateMessage(pres); textFileCreator.createTextFiles(pres); } private void createSvgImages(UploadedPresentation pres) { notifier.sendCreatingSvgImagesUpdateMessage(pres); svgImageCreator.createSvgImages(pres); } private void convertPdfToSwf(UploadedPresentation pres) { int numPages = pres.getNumberOfPages(); List<PdfToSwfSlide> slides = setupSlides(pres, numPages); CompletionService<PdfToSwfSlide> completionService = new ExecutorCompletionService<PdfToSwfSlide>( executor); generateSlides(pres, slides, completionService); } private void generateSlides(UploadedPresentation pres, List<PdfToSwfSlide> slides, CompletionService<PdfToSwfSlide> completionService) { long MAXWAIT = MAX_CONVERSION_TIME * 60 /* seconds */ * 1000 /* millis */; int slidesCompleted = 0; long presConvStart = System.currentTimeMillis(); for (final PdfToSwfSlide slide : slides) { long pageConvStart = System.currentTimeMillis(); Callable<PdfToSwfSlide> c = new Callable<PdfToSwfSlide>() { public PdfToSwfSlide call() { return slide.createSlide(); }; }; Future<PdfToSwfSlide> f = executor.submit(c); long endNanos = System.nanoTime() + CONVERSION_TIMEOUT; try { // Only wait for the remaining time budget long timeLeft = endNanos - System.nanoTime(); PdfToSwfSlide s = f.get(timeLeft, TimeUnit.NANOSECONDS); slidesCompleted++; notifier.sendConversionUpdateMessage(slidesCompleted, pres); } catch (ExecutionException e) { Map<String, Object> logData = new HashMap<String, Object>(); logData.put("meetingId", pres.getMeetingId()); logData.put("presId", pres.getId()); logData.put("filename", pres.getName()); logData.put("page", slide.getPageNumber()); Gson gson = new Gson(); String logStr = gson.toJson(logData); log.warn("ExecutionException while converting page: data={}", logStr); log.error(e.getMessage()); } catch (InterruptedException e) { Map<String, Object> logData = new HashMap<String, Object>(); logData.put("meetingId", pres.getMeetingId()); logData.put("presId", pres.getId()); logData.put("filename", pres.getName()); logData.put("page", slide.getPageNumber()); Gson gson = new Gson(); String logStr = gson.toJson(logData); log.warn("InterruptedException while converting page: data={}", logStr); Thread.currentThread().interrupt(); } catch (TimeoutException e) { Map<String, Object> logData = new HashMap<String, Object>(); logData.put("meetingId", pres.getMeetingId()); logData.put("presId", pres.getId()); logData.put("filename", pres.getName()); logData.put("page", slide.getPageNumber()); Gson gson = new Gson(); String logStr = gson.toJson(logData); log.warn("TimeoutException while converting page: data={}", logStr); f.cancel(true); } long pageConvEnd = System.currentTimeMillis(); Map<String, Object> logData = new HashMap<String, Object>(); logData.put("meetingId", pres.getMeetingId()); logData.put("presId", pres.getId()); logData.put("filename", pres.getName()); logData.put("page", slide.getPageNumber()); logData.put("conversionTime(sec)", (pageConvEnd - pageConvStart) / 1000); Gson gson = new Gson(); String logStr = gson.toJson(logData); log.debug("Page conversion duration(sec): data={}", logStr); } for (final PdfToSwfSlide slide : slides) { if (!slide.isDone()) { slide.generateBlankSlide(); Map<String, Object> logData = new HashMap<String, Object>(); logData.put("meetingId", pres.getMeetingId()); logData.put("presId", pres.getId()); logData.put("filename", pres.getName()); Gson gson = new Gson(); String logStr = gson.toJson(logData); log.warn("Creating blank slide: data={}", logStr); notifier.sendConversionUpdateMessage(slidesCompleted++, pres); } } long presConvEnd = System.currentTimeMillis(); Map<String, Object> logData = new HashMap<String, Object>(); logData.put("meetingId", pres.getMeetingId()); logData.put("presId", pres.getId()); logData.put("filename", pres.getName()); logData.put("conversionTime(sec)", (presConvEnd - presConvStart) / 1000); Gson gson = new Gson(); String logStr = gson.toJson(logData); log.debug("Presentation conversion duration (sec): data={}", logStr); } private List<PdfToSwfSlide> setupSlides(UploadedPresentation pres, int numPages) { List<PdfToSwfSlide> slides = new ArrayList<PdfToSwfSlide>(numPages); for (int page = 1; page <= numPages; page++) { PdfToSwfSlide slide = new PdfToSwfSlide(pres, page); slide.setBlankSlide(BLANK_SLIDE); slide.setMaxSwfFileSize(MAX_SWF_FILE_SIZE); slide.setPageConverter(pdfToSwfConverter); slides.add(slide); } return slides; } public void setCounterService(PageCounterService counterService) { this.counterService = counterService; } public void setPageConverter(PageConverter converter) { this.pdfToSwfConverter = converter; } public void setBlankSlide(String blankSlide) { this.BLANK_SLIDE = blankSlide; } public void setMaxSwfFileSize(int size) { this.MAX_SWF_FILE_SIZE = size; } public void setSvgImagesRequired(boolean svg) { this.svgImagesRequired = svg; } public void setThumbnailCreator(ThumbnailCreator thumbnailCreator) { this.thumbnailCreator = thumbnailCreator; } public void setTextFileCreator(TextFileCreator textFileCreator) { this.textFileCreator = textFileCreator; } public void setSvgImageCreator(SvgImageCreator svgImageCreator) { this.svgImageCreator = svgImageCreator; } public void setMaxConversionTime(int minutes) { MAX_CONVERSION_TIME = minutes * 60 * 1000; } public void setSwfSlidesGenerationProgressNotifier( SwfSlidesGenerationProgressNotifier notifier) { this.notifier = notifier; } }