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.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.httpclient.HttpClient; 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 PageExtractor pageExtractor; private DocumentConversionService documentConversionService; private String presentationBaseURL; private String presentationDir; private String BLANK_PRESENTATION; private ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3); public void stop() { scheduledThreadPool.shutdownNow(); } public void processUploadedPresentation(final UploadedPresentation uploadedPres) { /** * We delay processing of the presentation to make sure that the meeting has already been created. * Otherwise, the meeting won't get the conversion events. */ ScheduledFuture scheduledFuture = scheduledThreadPool.schedule(new Runnable() { public void run() { documentConversionService.processDocument(uploadedPres); } }, 5, TimeUnit.SECONDS); } 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 extractPresentationPage(final String sourceMeetingId, final String presentationId, final Integer presentationSlide, final String destinationMeetingId) { /** * We delay processing of the presentation to make sure that the meeting has already been created. * Otherwise, the meeting won't get the conversion events. */ ScheduledFuture scheduledFuture = scheduledThreadPool.schedule(new Runnable() { public void run() { extractPage(sourceMeetingId, presentationId, presentationSlide, destinationMeetingId) ; } }, 5, TimeUnit.SECONDS); } private void extractPage(final String sourceMeetingId, final String presentationId, final Integer presentationSlide, final String destinationMeetingId) { // Build the source meeting path File sourceMeetingPath = new File(presentationDir + File.separator + sourceMeetingId + File.separator + sourceMeetingId + File.separator + presentationId); // Find the source meeting presentation file 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 != null && 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); } File sourcePresentationFile; if (matches == null || matches.length != 1) { log.warn( "Not matching PDF file with prefix {} found at {}. Using the default blank PDF", sourceMeetingId, sourceMeetingPath); sourcePresentationFile = new File(BLANK_PRESENTATION); } else { sourcePresentationFile = matches[0]; } // Build the target meeting path 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; } public void setBlankPresentation(String blankPresentation) { this.BLANK_PRESENTATION = blankPresentation; } }