From 7851d54484fc10acf7d14fc3bff8333df0f1f4ab Mon Sep 17 00:00:00 2001 From: Daniel Petri Rocha Date: Fri, 29 Apr 2022 13:50:42 +0200 Subject: [PATCH] PDF file: include meeting and room name --- .../StoreExportJobInRedisPresAnnEvent.scala | 10 +++++ .../core/running/MeetingActor.scala | 44 ++++++++++++++----- .../redis/ExportAnnotationsActor.scala | 2 + .../endpoint/redis/RedisRecorderActor.scala | 2 +- .../common2/msgs/WhiteboardMsgs.scala | 2 + .../nginx-confs/presentation-slides.nginx | 4 +- export-annotations/master.js | 7 +-- export-annotations/package-lock.json | 43 ++++++++++++++++++ export-annotations/package.json | 1 + export-annotations/workers/collector.js | 28 ++++++------ export-annotations/workers/notifier.js | 12 +++-- export-annotations/workers/process.js | 38 +++++++++------- 12 files changed, 138 insertions(+), 55 deletions(-) diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/record/events/StoreExportJobInRedisPresAnnEvent.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/record/events/StoreExportJobInRedisPresAnnEvent.scala index 79332f71d8..8d01bcc9bc 100644 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/record/events/StoreExportJobInRedisPresAnnEvent.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/record/events/StoreExportJobInRedisPresAnnEvent.scala @@ -35,6 +35,14 @@ class StoreExportJobInRedisPresAnnEvent extends AbstractPresentationWithAnnotati eventMap.put(JOB_TYPE, jobType) } + def setMeetingName(meetingName: String) { + eventMap.put(MEETING_NAME, meetingName) + } + + def setPresName(presName: String) { + eventMap.put(PRES_NAME, presName) + } + def setPresId(presId: String) { eventMap.put(PRES_ID, presId) } @@ -63,6 +71,8 @@ class StoreExportJobInRedisPresAnnEvent extends AbstractPresentationWithAnnotati object StoreExportJobInRedisPresAnnEvent { protected final val JOB_ID = "jobId" protected final val JOB_TYPE = "jobType" + protected final val MEETING_NAME = "meetingName" + protected final val PRES_NAME = "presName" protected final val PRES_ID = "presId" protected final val PRES_LOCATION = "presLocation" protected final val ALL_PAGES = "allPages" diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/MeetingActor.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/MeetingActor.scala index dd5ec22658..f69b0d09dc 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/MeetingActor.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/MeetingActor.scala @@ -753,6 +753,7 @@ class MeetingActor( def handleMakePresentationWithAnnotationDownloadReqMsg(m: MakePresentationWithAnnotationDownloadReqMsg, state: MeetingState2x, liveMeeting: LiveMeeting): Unit = { val meetingId = liveMeeting.props.meetingProp.intId + val meetingName: String = liveMeeting.props.meetingProp.name // Whiteboard ID val presId: String = m.body.presId match { @@ -765,18 +766,31 @@ class MeetingActor( // Determine page amount val presentationPods: Vector[PresentationPod] = state.presentationPodManager.getAllPresentationPodsInMeeting() - val currentPres = presentationPods.flatMap(_.getCurrentPresentation()).head - val pageCount = currentPres.pages.size + val currentPres = presentationPods.flatMap(_.getCurrentPresentation()).headOption + + currentPres match { + case None => + log.error(s"No presentation set in meeting ${meetingId}") + return + case _ => () + } + + val pageCount = currentPres.get.pages.size val pagesRange: List[Int] = if (allPages) (1 to pageCount).toList else pages var storeAnnotationPages = new Array[PresentationPageForExport](pagesRange.size) var resultingPage = 0 for (pageNumber <- pagesRange) { + if (pageNumber < 1 || pageNumber > pageCount) { + println(pagesRange.length) + log.error(s"Page ${pageNumber} requested for export out of range, aborting") + return + } var whiteboardId = s"${presId}/${pageNumber.toString}" - val presentationPage: PresentationPage = currentPres.pages(whiteboardId) + val presentationPage: PresentationPage = currentPres.get.pages(whiteboardId) val xOffset: Double = presentationPage.xOffset val yOffset: Double = presentationPage.yOffset val widthRatio: Double = presentationPage.widthRatio @@ -796,7 +810,7 @@ class MeetingActor( // 2) Insert Export Job in Redis val jobType = "PresentationWithAnnotationDownloadJob" val presLocation = s"/var/bigbluebutton/${meetingId}/${meetingId}/${presId}" - val exportJob = new ExportJob(jobId, jobType, presId, presLocation, allPages, pagesRange, meetingId, "") + val exportJob = new ExportJob(jobId, jobType, meetingName, currentPres.get.name, presId, presLocation, allPages, pagesRange, meetingId, "") var job = buildStoreExportJobInRedisSysMsg(exportJob) outGW.send(job) } @@ -804,25 +818,33 @@ class MeetingActor( def handleExportPresentationWithAnnotationReqMsg(m: ExportPresentationWithAnnotationReqMsg, state: MeetingState2x, liveMeeting: LiveMeeting): Unit = { val meetingId = liveMeeting.props.meetingProp.intId + val meetingName: String = liveMeeting.props.meetingProp.name val userId = m.header.userId val presId: String = getMeetingInfoPresentationDetails.id val parentMeetingId: String = m.body.parentMeetingId val allPages: Boolean = m.body.allPages val presentationPods: Vector[PresentationPod] = state.presentationPodManager.getAllPresentationPodsInMeeting() - val currentPres = presentationPods.flatMap(_.getCurrentPresentation()).head - val currentPage: PresentationPage = PresentationInPod.getCurrentPage(currentPres).get + val currentPres = presentationPods.flatMap(_.getCurrentPresentation()).headOption - val pageCount = currentPres.pages.size + currentPres match { + case None => + log.error(s"No presentation set in meeting ${meetingId}") + return + case _ => () + } + + val currentPage: PresentationPage = PresentationInPod.getCurrentPage(currentPres.get).get + + val pageCount = currentPres.get.pages.size val pagesRange: List[Int] = if (allPages) (1 to pageCount).toList else List(currentPage.num) var storeAnnotationPages = new Array[PresentationPageForExport](pagesRange.size) var resultingPage = 0 for (pageNumber <- pagesRange) { - var whiteboardId = s"${presId}/${pageNumber.toString}" - val presentationPage: PresentationPage = currentPres.pages(whiteboardId) + val presentationPage: PresentationPage = currentPres.get.pages(whiteboardId) val xOffset: Double = presentationPage.xOffset val yOffset: Double = presentationPage.yOffset val widthRatio: Double = presentationPage.widthRatio @@ -837,7 +859,7 @@ class MeetingActor( val jobId = RandomStringGenerator.randomAlphanumericString(16) // Informs bbb-web about the token so that when we use it to upload the presentation, it is able to look it up in the list of tokens - outGW.send(buildPresentationUploadTokenSysPubMsg(parentMeetingId, userId, presentationUploadToken, currentPres.name)) + outGW.send(buildPresentationUploadTokenSysPubMsg(parentMeetingId, userId, presentationUploadToken, currentPres.get.name)) // 1) Send Annotations to Redis var annotations = new StoredAnnotations(jobId, presId, storeAnnotationPages) @@ -846,7 +868,7 @@ class MeetingActor( // 2) Insert Export Job in Redis val jobType: String = "PresentationWithAnnotationExportJob" val presLocation = s"/var/bigbluebutton/${meetingId}/${meetingId}/${presId}" - val exportJob = new ExportJob(jobId, jobType, presId, presLocation, allPages, pagesRange, parentMeetingId, presentationUploadToken) + val exportJob = new ExportJob(jobId, jobType, meetingName, currentPres.get.name, presId, presLocation, allPages, pagesRange, parentMeetingId, presentationUploadToken) var job = buildStoreExportJobInRedisSysMsg(exportJob) outGW.send(job) } diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/endpoint/redis/ExportAnnotationsActor.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/endpoint/redis/ExportAnnotationsActor.scala index 30acdf6205..7b7cb40159 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/endpoint/redis/ExportAnnotationsActor.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/endpoint/redis/ExportAnnotationsActor.scala @@ -75,6 +75,8 @@ class ExportAnnotationsActor( ev.setJobId(msg.body.exportJob.jobId) ev.setJobType(msg.body.exportJob.jobType) + ev.setMeetingName(msg.body.exportJob.meetingName) + ev.setPresName(msg.body.exportJob.presName) ev.setPresId(msg.body.exportJob.presId) ev.setPresLocation(msg.body.exportJob.presLocation) ev.setAllPages(msg.body.exportJob.allPages.toString) diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/endpoint/redis/RedisRecorderActor.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/endpoint/redis/RedisRecorderActor.scala index d6e5dd8f63..a106261155 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/endpoint/redis/RedisRecorderActor.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/endpoint/redis/RedisRecorderActor.scala @@ -142,7 +142,7 @@ class RedisRecorderActor( ev.setSenderId(msg.body.msg.sender.id) ev.setMessage(msg.body.msg.message) ev.setSenderRole(msg.body.msg.sender.role) - + val isModerator = msg.body.msg.sender.role == "MODERATOR" ev.setChatEmphasizedText(msg.body.msg.chatEmphasizedText && isModerator) diff --git a/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/WhiteboardMsgs.scala b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/WhiteboardMsgs.scala index 45eed23001..eb62113ab4 100755 --- a/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/WhiteboardMsgs.scala +++ b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/WhiteboardMsgs.scala @@ -21,6 +21,8 @@ case class StoredAnnotations( case class ExportJob( jobId: String, jobType: String, + meetingName: String, + presName: String, presId: String, presLocation: String, allPages: Boolean, diff --git a/bigbluebutton-web/nginx-confs/presentation-slides.nginx b/bigbluebutton-web/nginx-confs/presentation-slides.nginx index a04ba59318..4f0713f0f1 100644 --- a/bigbluebutton-web/nginx-confs/presentation-slides.nginx +++ b/bigbluebutton-web/nginx-confs/presentation-slides.nginx @@ -27,9 +27,9 @@ } } - location ~^\/bigbluebutton\/presentation\/(?[A-Za-z0-9\-]+)\/(?[A-Za-z0-9\-]+)\/(?[A-Za-z0-9\-]+)\/pdf\/(?[A-Za-z0-9]+)$ { + location ~^\/bigbluebutton\/presentation\/(?[A-Za-z0-9\-]+)\/(?[A-Za-z0-9\-]+)\/(?[A-Za-z0-9\-]+)\/pdf\/(?[A-Za-z0-9]+)/(?.*)$ { default_type application/pdf; - alias /var/bigbluebutton/$meeting_id_2/$meeting_id_2/$pres_id/pdfs/annotated_slides_$job_id.pdf; + alias /var/bigbluebutton/$meeting_id_2/$meeting_id_2/$pres_id/pdfs/$job_id/annotated_$filename.pdf; if ($bbb_loadbalancer_node) { add_header 'Access-Control-Allow-Origin' $bbb_loadbalancer_node always; } diff --git a/export-annotations/master.js b/export-annotations/master.js index 1f1548c683..95ab75c510 100644 --- a/export-annotations/master.js +++ b/export-annotations/master.js @@ -4,13 +4,14 @@ const fs = require('fs'); const redis = require('redis'); const { commandOptions } = require('redis'); const { Worker } = require('worker_threads'); +const path = require('path'); const logger = new Logger('presAnn Master'); logger.info("Running export-annotations"); const kickOffCollectorWorker = (jobId) => { return new Promise((resolve, reject) => { - const worker = new Worker('./workers/collector.js', { workerData: jobId }); + const worker = new Worker(path.join(__dirname, 'workers', 'collector.js'), { workerData: jobId }); worker.on('message', resolve); worker.on('error', reject); worker.on('exit', (code) => { @@ -44,11 +45,11 @@ const kickOffCollectorWorker = (jobId) => { const exportJob = JSON.parse(job.element); // Create folder in dropbox - let dropbox = `${config.shared.presAnnDropboxDir}/${exportJob.jobId}` + let dropbox = path.join(config.shared.presAnnDropboxDir, exportJob.jobId); fs.mkdirSync(dropbox, { recursive: true }) // Drop job into dropbox as JSON - fs.writeFile(`${dropbox}/job`, job.element, function(err) { + fs.writeFile(path.join(dropbox, 'job'), job.element, function(err) { if(err) { return logger.error(err); } }); diff --git a/export-annotations/package-lock.json b/export-annotations/package-lock.json index c43e427511..2d944eaef3 100644 --- a/export-annotations/package-lock.json +++ b/export-annotations/package-lock.json @@ -12,6 +12,7 @@ "form-data": "^4.0.0", "image-size": "^1.0.1", "redis": "^4.0.3", + "sanitize-filename": "^1.6.3", "xmlbuilder2": "^3.0.2" } }, @@ -288,11 +289,32 @@ "node": ">=4" } }, + "node_modules/sanitize-filename": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.3.tgz", + "integrity": "sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==", + "dependencies": { + "truncate-utf8-bytes": "^1.0.0" + } + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" }, + "node_modules/truncate-utf8-bytes": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", + "integrity": "sha1-QFkjkJWS1W94pYGENLC3hInKXys=", + "dependencies": { + "utf8-byte-length": "^1.0.1" + } + }, + "node_modules/utf8-byte-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz", + "integrity": "sha1-9F8VDExm7uloGGUFq5P8u4rWv2E=" + }, "node_modules/xmlbuilder2": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/xmlbuilder2/-/xmlbuilder2-3.0.2.tgz", @@ -529,11 +551,32 @@ "redis-errors": "^1.0.0" } }, + "sanitize-filename": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.3.tgz", + "integrity": "sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==", + "requires": { + "truncate-utf8-bytes": "^1.0.0" + } + }, "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" }, + "truncate-utf8-bytes": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", + "integrity": "sha1-QFkjkJWS1W94pYGENLC3hInKXys=", + "requires": { + "utf8-byte-length": "^1.0.1" + } + }, + "utf8-byte-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz", + "integrity": "sha1-9F8VDExm7uloGGUFq5P8u4rWv2E=" + }, "xmlbuilder2": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/xmlbuilder2/-/xmlbuilder2-3.0.2.tgz", diff --git a/export-annotations/package.json b/export-annotations/package.json index 927748598f..e965ff6df6 100644 --- a/export-annotations/package.json +++ b/export-annotations/package.json @@ -10,6 +10,7 @@ "form-data": "^4.0.0", "image-size": "^1.0.1", "redis": "^4.0.3", + "sanitize-filename": "^1.6.3", "xmlbuilder2": "^3.0.2" } } diff --git a/export-annotations/workers/collector.js b/export-annotations/workers/collector.js index 838f89d45c..9a760d0399 100644 --- a/export-annotations/workers/collector.js +++ b/export-annotations/workers/collector.js @@ -3,8 +3,8 @@ const config = require('../config'); const fs = require('fs'); const redis = require('redis'); const { execSync } = require("child_process"); - const { Worker, workerData, parentPort } = require('worker_threads') +const path = require('path'); const jobId = workerData; @@ -23,10 +23,10 @@ const kickOffProcessWorker = (jobId) => { }) } -let dropbox = `${config.shared.presAnnDropboxDir}/${jobId}` +let dropbox = path.join(config.shared.presAnnDropboxDir, jobId); // Takes the Job from the dropbox -let job = fs.readFileSync(`${dropbox}/job`); +let job = fs.readFileSync(path.join(dropbox, 'job')); let exportJob = JSON.parse(job); // Collect the annotations from Redis @@ -51,17 +51,17 @@ let exportJob = JSON.parse(job); let whiteboard = JSON.parse(annotations); let pages = JSON.parse(whiteboard.pages); - fs.writeFile(`${dropbox}/whiteboard`, annotations, function(err) { + fs.writeFile(path.join(dropbox, 'whiteboard'), annotations, function(err) { if(err) { return logger.error(err); } }); // Collect the Presentation Page files from the presentation directory - let path = `${exportJob.presLocation}/${exportJob.presId}`; - let pdfFileExists = fs.existsSync(`${path}.pdf`); + let presentationFile = path.join(exportJob.presLocation, exportJob.presId); + let pdfFileExists = fs.existsSync(`${presentationFile}.pdf`); for (let p of pages) { let pageNumber = p.page; - let file = `${dropbox}/slide${pageNumber}`; + let outputFile = path.join(dropbox, `slide${pageNumber}`); if(pdfFileExists) { let extactSlideAsPDFCommands = [ @@ -70,8 +70,8 @@ let exportJob = JSON.parse(job); '-f', pageNumber, '-l', pageNumber, '-singlefile', - `${path}.pdf`, - file + `${presentationFile}.pdf`, + outputFile ].join(' '); execSync(extactSlideAsPDFCommands, (error, stderr) => { @@ -85,19 +85,19 @@ let exportJob = JSON.parse(job); }) } - else if (fs.existsSync(`${path}.png`)) { - fs.copyFileSync(`${path}.png`, `${file}.png`); + else if (fs.existsSync(`${presentationFile}.png`)) { + fs.copyFileSync(`${presentationFile}.png`, `${outputFile}.png`); } - else if (fs.existsSync(`${path}.jpeg`)) { + else if (fs.existsSync(`${presentationFile}.jpeg`)) { let convertImageToPngCommands = [ 'convert', - `${path}.jpeg`, + `${presentationFile}.jpeg`, '-background', 'white', '-resize', '1600x1600', '-auto-orient', '-flatten', - `${file}.png` + `${outputFile}.png` ].join(' '); execSync(convertImageToPngCommands, (error, stderr) => { diff --git a/export-annotations/workers/notifier.js b/export-annotations/workers/notifier.js index 3c7d8aca46..31c7cc24f6 100644 --- a/export-annotations/workers/notifier.js +++ b/export-annotations/workers/notifier.js @@ -4,15 +4,16 @@ const fs = require('fs'); const FormData = require('form-data'); const redis = require('redis'); const axios = require('axios').default; +const path = require('path'); const { workerData, parentPort } = require('worker_threads') -const [jobType, jobId] = workerData; +const [jobType, jobId, filename] = workerData; const logger = new Logger('presAnn Notifier Worker'); const dropbox = `${config.shared.presAnnDropboxDir}/${jobId}` -let job = fs.readFileSync(`${dropbox}/job`); +let job = fs.readFileSync(path.join(dropbox, 'job')); let exportJob = JSON.parse(job); async function notifyMeetingActor() { @@ -25,7 +26,7 @@ async function notifyMeetingActor() { await client.connect(); client.on('error', (err) => logger.info('Redis Client Error', err)); - let link = `${config.notifier.protocol}://${config.notifier.host}/bigbluebutton/presentation/${exportJob.parentMeetingId}/${exportJob.parentMeetingId}/${exportJob.presId}/pdf/${jobId}`; + let link = `${config.notifier.protocol}://${config.notifier.host}/bigbluebutton/presentation/${exportJob.parentMeetingId}/${exportJob.parentMeetingId}/${exportJob.presId}/pdf/${jobId}/${filename}`; // Notify Meeting Actor of file availability by sending a message through Redis PubSub const notification = { envelope: { @@ -56,13 +57,10 @@ async function upload(exportJob) { let callbackUrl = `http://${config.bbbWeb.host}:${config.bbbWeb.port}/bigbluebutton/presentation/${exportJob.presentationUploadToken}/upload` let formData = new FormData(); - formData.append('presentation_name', 'annotated_slides.pdf'); - formData.append('Filename', 'annotated_slides'); formData.append('conference', exportJob.parentMeetingId); - formData.append('room', exportJob.parentMeetingId); formData.append('pod_id', config.notifier.pod_id); formData.append('is_downloadable', config.notifier.is_downloadable); - formData.append('fileUpload', fs.createReadStream(`${exportJob.presLocation}/pdfs/annotated_slides_${jobId}.pdf`)); + formData.append('fileUpload', fs.createReadStream(`${exportJob.presLocation}/pdfs/${jobId}/${filename}.pdf`)); let res = await axios.post(callbackUrl, formData, { headers: formData.getHeaders() }); logger.info(`Upload of job ${exportJob.jobId} returned ${res.data}`); diff --git a/export-annotations/workers/process.js b/export-annotations/workers/process.js index 0389fe00cc..ab6027f3e0 100644 --- a/export-annotations/workers/process.js +++ b/export-annotations/workers/process.js @@ -5,6 +5,8 @@ const sizeOf = require('image-size'); const { create } = require('xmlbuilder2', { encoding: 'utf-8' }); const { execSync } = require("child_process"); const { Worker, workerData, parentPort } = require('worker_threads'); +const path = require('path'); +const sanitize = require("sanitize-filename"); const jobId = workerData; const MAGIC_MYSTERY_NUMBER = 2; @@ -12,9 +14,9 @@ const MAGIC_MYSTERY_NUMBER = 2; const logger = new Logger('presAnn Process Worker'); logger.info("Processing PDF for job " + jobId); -const kickOffNotifierWorker = (jobType) => { +const kickOffNotifierWorker = (jobType, sanitizedFilename) => { return new Promise((resolve, reject) => { - const worker = new Worker('./workers/notifier.js', { workerData: [jobType, jobId] }); + const worker = new Worker('./workers/notifier.js', { workerData: [jobType, jobId, sanitizedFilename] }); worker.on('message', resolve); worker.on('error', reject); worker.on('exit', (code) => { @@ -43,7 +45,7 @@ function render_HTMLTextBox(htmlFilePath, id, width, height) { '--crop-h', height, '--log-level', 'none', '--quality', '100', - htmlFilePath, `${dropbox}/text${id}.png` + htmlFilePath, path.join(dropbox, `text${id}.png`) ] execSync(commands.join(' '), (error, stderr) => { @@ -176,8 +178,8 @@ function overlay_poll(svg, annotation, w, h) { let poll_width = Math.round(scale_shape(w, annotation.points[2])); let poll_height = Math.round(scale_shape(h, annotation.points[3])); let pollId = annotation.id.replace(/\//g, ''); - let pollSVG = `${dropbox}/poll-${pollId}.svg` - let pollJSON = `${dropbox}/poll-${pollId}.json` + let pollSVG = path.join(dropbox, `poll-${pollId}.svg`); + let pollJSON = path.join(dropbox, `poll-${pollId}.json`); // Rename 'numVotes' key to 'num_votes' let pollJSONContent = annotation.result.map(result => { @@ -281,7 +283,7 @@ function overlay_text(svg, annotation, w, h) {

`; - var htmlFilePath = `${dropbox}/text${annotation.id}.html` + var htmlFilePath = path.join(dropbox, `text${annotation.id}.html`) fs.writeFileSync(htmlFilePath, html, function (err) { if (err) logger.error(err) @@ -331,19 +333,19 @@ function overlay_annotations(svg, currentSlideAnnotations, w, h) { // Process the presentation pages and annotations into a PDF file // 1. Get the job -const dropbox = `${config.shared.presAnnDropboxDir}/${jobId}` -let job = fs.readFileSync(`${dropbox}/job`); +const dropbox = path.join(config.shared.presAnnDropboxDir, jobId); +let job = fs.readFileSync(path.join(dropbox, 'job')); let exportJob = JSON.parse(job); // 2. Get the annotations -let annotations = fs.readFileSync(`${dropbox}/whiteboard`); +let annotations = fs.readFileSync(path.join(dropbox, 'whiteboard')); let whiteboard = JSON.parse(annotations); let pages = JSON.parse(whiteboard.pages); let ghostScriptInput = "" // 3. Convert annotations to SVG for (let currentSlide of pages) { - var dimensions = sizeOf(`${dropbox}/slide${currentSlide.page}.png`); + var dimensions = sizeOf(path.join(dropbox, `slide${currentSlide.page}.png`)); var slideWidth = dimensions.width; var slideHeight = dimensions.height; @@ -381,8 +383,8 @@ for (let currentSlide of pages) { svg = svg.end({ prettyPrint: true }); // Write annotated SVG file - let SVGfile = `${dropbox}/annotated-slide${currentSlide.page}.svg` - let PDFfile = `${dropbox}/annotated-slide${currentSlide.page}.pdf` + let SVGfile = path.join(dropbox, `annotated-slide${currentSlide.page}.svg`) + let PDFfile = path.join(dropbox, `annotated-slide${currentSlide.page}.pdf`) fs.writeFileSync(SVGfile, svg, function(err) { if(err) { return logger.error(err); } @@ -408,14 +410,16 @@ for (let currentSlide of pages) { } // Create PDF output directory if it doesn't exist -let output_dir = `${exportJob.presLocation}/pdfs`; -if (!fs.existsSync(output_dir)) { fs.mkdirSync(output_dir); } +let output_dir = path.join(exportJob.presLocation, 'pdfs', jobId); +let filename = sanitize(`annotated_${exportJob.meetingName}_${path.parse(exportJob.presName).name}`).replace(/\s/g, '_'); + +if (!fs.existsSync(output_dir)) { fs.mkdirSync(output_dir, { recursive: true }); } let mergePDFs = [ 'gs', '-dNOPAUSE', '-sDEVICE=pdfwrite', - `-sOUTPUTFILE=${output_dir}/annotated_slides_${jobId}.pdf`, + `-sOUTPUTFILE="${path.join(output_dir, `${filename}.pdf`)}"`, `-dBATCH`, ghostScriptInput, ].join(' '); @@ -432,8 +436,8 @@ execSync(mergePDFs, (error, stderr) => { }); // Launch Notifier Worker depending on job type -logger.info(`Saved PDF at ${output_dir}/annotated_slides_${jobId}.pdf`); +logger.info(`Saved PDF at ${output_dir}/${jobId}/${filename}.pdf`); -kickOffNotifierWorker(exportJob.jobType); +kickOffNotifierWorker(exportJob.jobType, filename); parentPort.postMessage({ message: workerData })