1 var REST = library.REST.REST();
  2 var Request = library.Request.build();
  3 var myDownloadTempDir = null;
  4 
  5 var kS3Enabled = args("aws.defaultBucket", "") !== "";
  6 
  7 /**
  8  * Used to increment files with the same name  test.txt becomes test 1.txt
  9  * @param theParentPath
 10  * @param theOldName
 11  * @returns {string}
 12  */
 13 function incrementName(theParentPath, theOldName) {
 14   var anExtension = theOldName.substring(
 15     theOldName.lastIndexOf("."),
 16     theOldName.length
 17   );
 18   var aFileNumber;
 19   var aNewName;
 20   if (theOldName.lastIndexOf(" ") !== -1) {
 21     aNewName = theOldName.substring(0, theOldName.lastIndexOf(" "));
 22     aFileNumber = theOldName.substring(
 23       theOldName.lastIndexOf(" "),
 24       theOldName.lastIndexOf(anExtension)
 25     );
 26     if (aFileNumber == null || aFileNumber === " ") {
 27       aFileNumber = 0;
 28     } else {
 29       var aMightBeNumber = aFileNumber;
 30       aFileNumber = parseInt(aFileNumber);
 31       if (isNaN(aFileNumber)) {
 32         //aFileNumber isn't a number. add the original back to the name
 33         aNewName += aMightBeNumber;
 34         aFileNumber = 0;
 35       }
 36     }
 37   } else {
 38     aFileNumber = 0;
 39     aNewName = theOldName.substring(0, theOldName.lastIndexOf("."));
 40   }
 41   aFileNumber++;
 42   while (
 43     fileManager.isFile(
 44       theParentPath + aNewName + " " + aFileNumber + anExtension
 45     )
 46   ) {
 47     aFileNumber++;
 48   }
 49   return aNewName + " " + aFileNumber + anExtension;
 50 }
 51 
 52 function upload() {
 53   var aDest = REST.getFullPath(context.getParameter("dest"));
 54   var aDestId = context.getParameter("destId");
 55   var aManagedFolder;
 56   if (!!aDestId) {
 57     aManagedFolder = fileManager.getFolderObjectById(aDestId);
 58     if (!aManagedFolder) {
 59       REST.pushError(
 60         REST.errors.e404,
 61         "The dest folder does not exist by id: " + aDestId
 62       );
 63       return;
 64     }
 65   }
 66 
 67   var aUnzip = context.getParameter("unzip");
 68   var aVersion = context.getParameter("version");
 69   if (!aVersion) {
 70     aVersion = context.getParameter("replace");
 71     if (!!aVersion) {
 72       logger.info(
 73         "'replace' parameter is deprecated, should be using 'version'."
 74       );
 75     }
 76   }
 77   var aDownloadedFiles = downloadFiles(context.getParameter("urls"));
 78   try {
 79     var anUploadedFile = context.getUploadedFile();
 80     if (anUploadedFile == null && aDownloadedFiles.length === 0) {
 81       REST.pushError(
 82         REST.errors.e400,
 83         "A file was not attached to the form-data of the Body of the POST"
 84       );
 85       return;
 86     }
 87     var aFiles = [];
 88     if (anUploadedFile != null) {
 89       if (anUploadedFile.constructor === Array) {
 90         for (var i = 0; i < anUploadedFile.length; i++) {
 91           aFiles.push(anUploadedFile[i]);
 92         }
 93       } else {
 94         aFiles.push(anUploadedFile);
 95       }
 96     }
 97     if (aDownloadedFiles.length > 0) {
 98       for (i = 0; i < aDownloadedFiles.length; i++) {
 99         aFiles.push(aDownloadedFiles[i]);
100       }
101     }
102     // Create the destination directory path if it does not already exist
103     if (!aManagedFolder) {
104       var aPathArray = aDest.toString().split("/");
105       var aPath = "";
106       for (i = 0; i < aPathArray.length; i++) {
107         // Continue on empty paths.
108         if (aPathArray[i] === "") {
109           continue;
110         }
111         aPath += aPathArray[i] + "/";
112 
113         //Only import unmanaged folders
114         var aDoImport = !fileManager.isManagedFolderPath(aPath);
115         var importingFolder = fileManager.folderNewByPath(aPath);
116 
117         //Exclude the renditions folder from import
118         if (aDoImport && importingFolder.path.indexOf("Renditions") !== 0) {
119           fileManager.folderImport(importingFolder, false);
120         }
121       }
122       aManagedFolder = new ManagedFolder(aDest);
123     }
124     if (aManagedFolder == null) {
125       REST.pushError(REST.errors.e404, "Folder not found: " + aDest);
126       return;
127     }
128 
129     if (kS3Enabled) {
130       uploadToS3(
131         aFiles.length === 1 ? aFiles[0] : aFiles,
132         aManagedFolder.path,
133         aUnzip,
134         aVersion
135       );
136     } else {
137       doUpload(aFiles, aManagedFolder.path, aUnzip, aVersion);
138     }
139 
140     if (myDownloadTempDir != null) {
141       fileManager.folderDelete(myDownloadTempDir);
142       myDownloadTempDir = null;
143     }
144   } catch (err) {
145     REST.pushError(REST.errors.e500, "Failed to upload to: " + aDest, err);
146   }
147 }
148 
149 /**
150  * Copies the xmp from the xmpAsset to theAsset and keeps both assets unmanaged.
151  *
152  * @param theAsset the destination asset.
153  * @param theXmpAsset the asset with the source XMP.
154  */
155 function setXmp(theAsset, theXmpAsset) {
156   var aSourceMeta =
157     theXmpAsset.xmp && theXmpAsset.xmp.meta ? theXmpAsset.xmp.meta : null;
158   var aDestMeta = theAsset.xmp && theAsset.xmp.meta ? theAsset.xmp.meta : null;
159 
160   if (aSourceMeta != null && aDestMeta != null) {
161     logger.info("---------- Apply source meta");
162     var aProperties = aSourceMeta.getFields();
163     for (var i = 0; i < aProperties.length; i++) {
164       var aProperty = aProperties[i];
165       var aField = aSourceMeta.getField(aProperty);
166       aDestMeta.addField(aProperty, aField);
167     }
168   } else {
169     logger.info("---------- Still o source meta");
170   }
171   theAsset.writeXmp();
172 }
173 
174 /**
175  *  Perform S3 upload(s)
176  *
177  * @param theUploadedFile the file(s) being uploaded
178  * @param theDestination the destination folder
179  * @param theUnzip should we unzip?
180  * @param theVersion should we create a new version?
181  */
182 function uploadToS3(theUploadedFile, theDestination, theUnzip, theVersion) {
183   if (theUploadedFile.constructor === Array) {
184     for (var i = 0; i < theUploadedFile.length; i++) {
185       logger.warning("Uploading: " + i);
186       var aUploadedFile = theUploadedFile[i];
187 
188       var aFileExtension = aUploadedFile.name.split(".").pop();
189       var isLoneFile = true;
190 
191       if (aFileExtension !== "xmp") {
192         // look for side-car .xmp files and apply them
193         for (var j = 0; j < theUploadedFile.length; j++) {
194           if (
195             theUploadedFile[j].name ===
196             aUploadedFile.name.split(".")[0] + ".xmp"
197           ) {
198             // Found a side-car xmp file, apply it and continue on
199             setXmp(aUploadedFile, theUploadedFile[j]);
200             break;
201           }
202         }
203       } else {
204         for (var k = 0; k < theUploadedFile.length; k++) {
205           if (
206             aUploadedFile.name ===
207             theUploadedFile[k].name.split(".")[0] + ".xmp"
208           ) {
209             // File is a side-car xmp, ignore it
210             isLoneFile = false;
211             break;
212           }
213         }
214       }
215 
216       // Upload to S3 if it's not a side-car xmp
217       if (isLoneFile) {
218         uploadS3File(aUploadedFile, theDestination, theUnzip, theVersion);
219       }
220     }
221   } else {
222     uploadS3File(theUploadedFile, theDestination, theUnzip, theVersion);
223   }
224 }
225 
226 /**
227  * Upload a single file or archive to S3
228  *
229  * @param theUploadedFile the file to upload
230  * @param theDestination the destination
231  * @param theUnzip should we unzip?
232  * @param theVersion should we create a new version?
233  */
234 function uploadS3File(theUploadedFile, theDestination, theUnzip, theVersion) {
235   // Handle Unzipping
236   var anIsAsyncImport = isAsyncImport();
237   if (theUnzip && theUploadedFile.name.endsWith(".zip")) {
238     if (theUploadedFile.name.indexOf(".zip") > 0 && theUnzip === true) {
239       var aDecompressedDir = fileManager.decompressFiles(
240         theUploadedFile,
241         new ManagedFolder(theDestination),
242         "UTF-8",
243         false,
244         "",
245         anIsAsyncImport
246       );
247       fileManager.fileDelete(theUploadedFile);
248 
249       if (REST.isFile(aDecompressedDir)) {
250         // Upload unzipped asset to S3
251         doS3Upload(
252           aDecompressedDir,
253           theDestination,
254           anIsAsyncImport,
255           theVersion
256         );
257         return;
258       }
259       var anIterator = aDecompressedDir.getContents();
260       while (anIterator.hasNext()) {
261         var aFileOrFolder = anIterator.next();
262         if (REST.isFile(aFileOrFolder)) {
263           // Upload unzipped asset to S3
264           doS3Upload(
265             aFileOrFolder,
266             theDestination,
267             anIsAsyncImport,
268             theVersion
269           );
270           fileManager.fileDelete(aFileOrFolder);
271         }
272       }
273       return;
274     }
275   }
276 
277   // Upload asset to S3
278   doS3Upload(
279     theUploadedFile,
280     theDestination,
281     anIsAsyncImport,
282     theVersion
283   );
284 }
285 
286 /**
287  * Performs S3 upload.
288  *
289  * @param theFile the file to upload
290  * @param theDestination the destination folder
291  * @param theIsAsyncImport is asynchronous import
292  * @param theVersion should we create a new version?
293  */
294 function doS3Upload(theFile, theDestination, theIsAsyncImport, theVersion) {
295   if (fileManager.isManagedFilePath(theDestination + theFile.name) && theVersion)
296   {
297     var aDestFile = new ManagedFile(theDestination + theFile.name);
298     checkin(aDestFile, theFile);
299     pushUploadedAsset(fileManager.getFileObjectById(aDestFile.assetId));
300     return;
301   }
302   if (!!theIsAsyncImport)
303   {
304     var anActionId = s3Manager.doS3UploadAsync(
305             context.getUser().username,
306             theFile,
307             theDestination
308     );
309     pushAsyncUploadedAsset(theFile, theDestination, anActionId);
310   } else
311   {
312     var anAssetId = s3Manager.doS3Upload(
313             context.getUser().username,
314             theFile,
315             theDestination
316     );
317     var aNewAsset = fileManager.getFileObjectById(anAssetId);
318     pushUploadedAsset(aNewAsset);
319   }
320 }
321 
322 function doUpload(theUploadedFiles, theDestination, theUnzip, theVersion) {
323   var anIsAsyncImport = isAsyncImport();
324   // Versioning parameters
325   var aComment =
326     context.getParameter("message") === undefined
327       ? ""
328       : context.getParameter("comment");
329   var replaceName =
330     context.getParameter("replaceName") === "true" ||
331     context.getParameter("replaceName") === true;
332   var aReplaceMetadata =
333     context.getParameter("replaceMetadata") === "true" ||
334     context.getParameter("replaceMetadata") === true;
335 
336   for (var i = 0; i < theUploadedFiles.length; i++) {
337     var aFileExtension = theUploadedFiles[i].name.split(".").pop();
338     var isStandAloneAsset = true;
339     var aFileToUpload = theUploadedFiles[i];
340 
341     if (aFileExtension !== "xmp") {
342       // if it's not an XMP file, see if it has a matching XMP to stick to it.
343       for (var j = 0; j < theUploadedFiles.length; j++) {
344         // Found a matching .XMP add to the mapping to apply it later
345         if (
346           theUploadedFiles[j].name ===
347           aFileToUpload.name.split(".")[0] + ".xmp"
348         ) {
349           setXmp(aFileToUpload, theUploadedFiles[j]);
350           break;
351         }
352       }
353     } else {
354       // If it is an XMP file, see if it's a lone XMP (no matching asset), if so, upload like a normal data asset
355       for (var j = 0; j < theUploadedFiles.length; j++) {
356         if (
357           theUploadedFiles[j].name.split(".").pop() !== "xmp" &&
358           theUploadedFiles[j].name.split(".")[0] ===
359             aFileToUpload.name.split(".")[0]
360         ) {
361           isStandAloneAsset = false;
362           break;
363         }
364       }
365     }
366 
367     // this file is an xmp for another asset we'll process later
368     if (!isStandAloneAsset) {
369       continue;
370     }
371 
372     if (
373       fileManager.isManagedFilePath(theDestination + aFileToUpload.name) &&
374       theVersion &&
375       !theUnzip
376     ) {
377       var aDestFile = new ManagedFile(theDestination + aFileToUpload.name);
378       versionManager.createNewVersion(
379         aDestFile.assetId,
380         aFileToUpload,
381         aComment,
382         replaceName,
383         aReplaceMetadata
384       );
385       pushUploadedAsset(fileManager.getFileObjectById(aDestFile.assetId));
386     } else if (aFileToUpload.name.indexOf(".zip") > 0 && theUnzip === true) {
387       var aDecompressedDir = fileManager.decompressFiles(
388         aFileToUpload,
389         new ManagedFolder(theDestination),
390         "UTF-8",
391         false,
392         "",
393         anIsAsyncImport
394       );
395       fileManager.fileDelete(aFileToUpload);
396       if (REST.isFile(aDecompressedDir)) {
397         finishUpload(aDecompressedDir, theDestination, anIsAsyncImport);
398         return;
399       }
400       var anIterator = aDecompressedDir.getContents();
401       while (anIterator.hasNext()) {
402         var aFileOrFolder = anIterator.next();
403         if (REST.isFile(aFileOrFolder)) {
404           finishUpload(aFileOrFolder, theDestination, anIsAsyncImport);
405         }
406       }
407     } else {
408       var anUploaded = fileManager.fileMove(
409         aFileToUpload,
410         new ManagedFolder(theDestination),
411         theVersion,
412         null,
413         anIsAsyncImport
414       );
415       finishUpload(anUploaded, theDestination, anIsAsyncImport);
416     }
417   }
418 }
419 
420 /**
421  * Finishes the standard upload.
422  *
423  * @param theFile the uploaded file
424  * @param theDestination the destination folder
425  * @param theIsAsyncImport is asynchronous import
426  */
427 function finishUpload(theFile, theDestination, theIsAsyncImport)
428 {
429   if (!!theIsAsyncImport)
430   {
431     var anActionId = fileManager.fileImportAsync(theFile);
432     pushAsyncUploadedAsset(theFile, theDestination, anActionId);
433   } else
434   {
435     pushUploadedAsset(theFile);
436   }
437 }
438 
439 /**
440  * Check in the working copy as a new version of the asset.
441  *
442  * @param theDestFile the destination file
443  * @param theSourceFile the source file
444  */
445 function checkin(theDestFile, theSourceFile)
446 {
447   try
448   {
449     var aStartTime = new Date();
450     if (theSourceFile == null)
451     {
452       throw "A file was not attached to the form-data of the Body of the POST";
453     }
454     logger.info("Checking in upload: " + theSourceFile.path);
455     checkoutManager.checkout(theDestFile);
456     if (
457             !checkoutManager.checkinWorkingCopy(
458                     theDestFile,
459                     "Checkin from REST API",
460                     theSourceFile
461             )
462     )
463     {
464       checkoutManager.cancelCheckout(theDestFile);
465       throw "Unable to checkin file, check MediaBeacon logs";
466     }
467     var i = 0;
468     while (i < 10)
469     {
470       try
471       {
472         fileManager.fileDelete(theSourceFile);
473         break;
474       } catch (theE)
475       {
476         logger.info("Failed to delete, trying again");
477         workflowManager.sleep(1);
478         i++;
479       }
480     }
481     var aMetric = {
482       action: "Checkin from 'REST API'",
483       subject: "Asset",
484       user_name: "System",
485       datestamp: new Date(),
486       elapsed_time: new Date() - aStartTime,
487       path: theDestFile.path,
488       asset_id: theDestFile.assetId,
489       description:
490               "REST API Checkin: " + theDestFile.path + ", SIZE: " + theDestFile.length,
491     };
492     metricsManager.addMetric(aMetric);
493   } catch (theE)
494   {
495     throw "Failed to upload: " + theE;
496   }
497 }
498 
499 /**
500  * Writes the uploaded user to the file and adds it to the response
501  * @param theAsset
502  */
503 function pushUploadedAsset(theAsset) {
504   var aProperty = new Property(
505     "http://mediabeacon.com/ns/default/1.0/",
506     "uploadUser"
507   );
508   theAsset.xmp.meta.addField(aProperty, context.getUser().username);
509   theAsset.writeXmp();
510   var aStub = {};
511   var anAssetAdded = REST.triggerAssetBasedWorkflows(
512     REST.AssetTriggers.Added,
513     theAsset
514   );
515   if (anAssetAdded) {
516     theAsset = fileManager.getFileObjectById(theAsset.assetId);
517     aStub.triggerAssetAdded = anAssetAdded.ok;
518   }
519   REST.push(theAsset, aStub);
520 }
521 
522 /**
523  * Writes data to the response.
524  *
525  * @param theAsset the uploaded asset
526  * @param theDestination the destination folder
527  * @param theActionId the action id
528  */
529 function pushAsyncUploadedAsset(theAsset, theDestination, theActionId) {
530   REST.push(
531           null,
532           {
533             name: theAsset.name,
534             path: theDestination + theAsset.name,
535             actionId: theActionId
536           }
537   );
538 }
539 
540 /**
541  * Is asynchronous import?
542  */
543 function isAsyncImport() {
544   return context.getParameter("asyncImport") === "true" ||
545           context.getParameter("asyncImport") === true;
546 }
547 
548 /**
549  * Downloads files by given url(s).
550  *
551  * @param theUrls the url(s)
552  */
553 function downloadFiles(theUrls) {
554   var aRes = [];
555   if (theUrls == null) {
556     return aRes;
557   }
558   for (var i = 0; i < theUrls.length; i++) {
559     var aFileName = theUrls[i].substr(theUrls[i].lastIndexOf("/") + 1);
560     aFileName = aFileName.split("?")[0];
561     var aFile = Request.get(theUrls[i], { dataType: "file", selfSigned: true, fileName: aFileName });
562     myDownloadTempDir = aFile.parent;
563     aRes.push(aFile);
564   }
565   return aRes;
566 }
567 
568 /**
569  * @name Upload
570  * @class Upload an asset into MediaBeacon
571  * @description For every asset defined in "data", this endpoint sets the xmp from the given xmp string
572  * @param file  the file needs to be added to the body of the POST in the form data with the name "file"
573  * @param dest the path to the destination folder "upload/here/"
574  * @param destId the directory id of the destination folder.
575  * @param [unzip=false] unzip the uploaded files
576  * @param [version=false] if a file already exists in the destination, new version it with the uploaded file
577  * @param [verbose=false] Setting this to true will collect a variety of default values for each asset.
578  * @param [fields] An array of field id's to collect the values for each asset
579  * @param [message] Comment for versioned file
580  * @param [replaceMetadata=false] Replace metadata for versioned file
581  * @param [replaceName=false] Replace name for versioned file
582  * @param [triggerAssetBasedWorkflow=true] set to false to avoid triggering other asset added workflows
583  * @param [asyncImport=false] set to true to import the uploaded file asynchronously
584  * @returns [{assetInfo}, ... ]
585  *
586  * @deprecated [replace=false] use 'version' instead, now it creates a new version if the file already exists.
587  *
588  * @example /wf/restapi/v2/upload
589  *
590  * Parameters:
591  * file={{FILE DATA IN POST BODY}}
592  * dest="upload/here/"
593  * verbose=true
594  *
595  * Response:
596  [
597  {
598 		 "id": 201629401,
599 		 "name": "heic0006a_orig.tif",
600 		 "path": "upload/here/heic0006a_orig.tif",
601 		 "height": 746,
602 		 "width": 1500,
603 		 "bytes": 1459026,
604 		 "lastModified": 1508187662000,
605 		 "mimeType": "image/tiff",
606 		 "previews": {
607 			 "thumbnail": "../servlet/jb.view?table=thumbnails&col=thumbnails&id=pe_323031363239343031",
608 			 "viewex": "../servlet/jb.view?table=viewex&col=viewex&id=pe_323031363239343031",
609 			 "downloadUrl": "../servlet/dload?id=pe_323031363239343031"
610 		 }
611 	 }
612  ]
613 
614  * @example /wf/restapi/v2/upload
615  *
616  * Parameters:
617  * file={{ZIPPED FILE}}
618  * dest="upload/here/"
619  * unzip=true
620  * verbose=true
621  *
622  * Response:
623  [
624  {
625 		 "name": "hubble",
626 		 "path": "upload/here/",
627 		 "resolver": "directory://162",
628 		 "assets": [
629 			 {
630 				 "id": 201629404,
631 				 "name": "heic0006a_orig.tif",
632 				 "path": "upload/here/heic0006a_orig.tif",
633 				 "height": 746,
634 				 "width": 1500,
635 				 "bytes": 1459026,
636 				 "lastModified": 1262635000,
637 				 "mimeType": "image/tiff",
638 				 "previews": {
639 					 "thumbnail": "../servlet/jb.view?table=thumbnails&col=thumbnails&id=pe_323031363239343034",
640 					 "viewex": "../servlet/jb.view?table=viewex&col=viewex&id=pe_323031363239343034",
641 					 "downloadUrl": "../servlet/dload?id=pe_323031363239343034"
642 				 }
643 			 },
644 			 {
645 				 "id": 201629405,
646 				 "name": "heic0106a1_orig.tif",
647 				 "path": "upload/here/heic0106a1_orig.tif",
648 				 "height": 573,
649 				 "width": 564,
650 				 "bytes": 817080,
651 				 "lastModified": 1262635000,
652 				 "mimeType": "image/tiff",
653 				 "previews": {
654 					 "thumbnail": "../servlet/jb.view?table=thumbnails&col=thumbnails&id=pe_323031363239343035",
655 					 "viewex": "../servlet/jb.view?table=viewex&col=viewex&id=pe_323031363239343035",
656 					 "downloadUrl": "../servlet/dload?id=pe_323031363239343035"
657 				 }
658 			 }
659 		 ]
660 	 }
661  ]
662 
663  * @example /wf/restapi/v2/upload
664  *
665  * Parameters:
666  * file={{FILE DATA IN POST BODY}}
667  * destId=123
668  * verbose=true
669  *
670  * Response:
671  [
672     {
673 		 "id": 201629401,
674 		 "name": "heic0006a_orig.tif",
675 		 "path": "upload/here/heic0006a_orig.tif",
676 		 "directoryId": 123
677 		 "height": 746,
678 		 "width": 1500,
679 		 "bytes": 1459026,
680 		 "lastModified": 1508187662000,
681 		 "mimeType": "image/tiff",
682 		 "previews": {
683 			 "thumbnail": "../servlet/jb.view?table=thumbnails&col=thumbnails&id=pe_323031363239343031",
684 			 "viewex": "../servlet/jb.view?table=viewex&col=viewex&id=pe_323031363239343031",
685 			 "downloadUrl": "../servlet/dload?id=pe_323031363239343031"
686 		 }
687 	 }
688  ]
689 
690  * @example /wf/restapi/v2/upload
691  *
692  * Parameters:
693  * file={{FILE DATA IN POST BODY}}
694  * dest="upload/here/"
695  * verbose=true
696  * asyncImport=true
697  *
698  * Response:
699  [
700 	 {
701 		 "name": "heic0006a_orig.tif",
702 		 "path": "upload/here/heic0006a_orig.tif",
703 		 "actionId": "12AF58A0-6C54-4413-91D7-D1A521B9D6D2"
704 	 }
705  ]
706  */
707 function main() {
708   if (!context.getUser().isPermissionEnabled("fileUpload")) {
709     REST.pushError(REST.errors.e403, "upload permission is not enabled");
710     return REST.execute();
711   }
712   REST.setCallback(upload);
713   return REST.execute();
714 }
715 main();
716