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