UNPKG

@openveo/publish

Version:
1,139 lines (964 loc) 33.8 kB
'use strict'; /** * @module publish/packages/Package */ var events = require('events'); var fs = require('fs'); var path = require('path'); var util = require('util'); var async = require('async'); var StateMachine = require('javascript-state-machine'); var openVeoApi = require('@openveo/api'); var configDir = openVeoApi.fileSystem.getConfDir(); var mediaPlatformFactory = process.requirePublish('app/server/providers/mediaPlatforms/factory.js'); var publishConf = require(path.join(configDir, 'publish/publishConf.json')); var videoPlatformConf = require(path.join(configDir, 'publish/videoPlatformConf.json')); var ERRORS = process.requirePublish('app/server/packages/errors.js'); var STATES = process.requirePublish('app/server/packages/states.js'); var PackageError = process.requirePublish('app/server/packages/PackageError.js'); var ResourceFilter = openVeoApi.storages.ResourceFilter; /** * Fired when an error occurred while processing the package. * * @event module:publish/packages/Package~Package#error * @property {Error} error The error */ /** * Fired when package processing has succeed. * * @event module:publish/packages/Package~Package#complete * @property {Object} package The processed package */ /** * Fired when package state has changed. * * @event module:publish/packages/Package~Package#stateChanged * @property {Object} package The processed package */ /** * Defines a Package to manage publication of a media file. * * @class Package * @constructor * @param {Object} mediaPackage Information about the media * @param {module:publish/providers/VideoProvider~VideoProvider} videoProvider Media provider * @param {module:publish/providers/PoiProvider~PoiProvider} poiProvider Points of interest provider */ function Package(mediaPackage, videoProvider, poiProvider) { // Validate temporary directory if (!publishConf.videoTmpDir || (typeof publishConf.videoTmpDir !== 'string')) this.emit('error', new PackageError('videoTmpDir in publishConf.json must be a String'), ERRORS.INVALID_CONFIGURATION); Object.defineProperties(this, /** @lends module:publish/packages/Package~Package */ { /** * Publish configuration. * * @type {Object} * @instance * @readonly */ publishConf: {value: publishConf}, /** * Media provider. * * @type {module:publish/providers/VideoProvider~VideoProvider} * @instance * @readonly */ videoProvider: {value: videoProvider}, /** * Points of interest provider. * * @type {module:publish/providers/PoiProvider~PoiProvider} * @instance * @readonly */ poiProvider: {value: poiProvider}, /** * Media package description object. * * @type {Object} * @instance */ mediaPackage: {value: mediaPackage, writable: true}, /** * Video platforms configuration object from videoPlatformConf.json file. * * @type {Object} * @instance * @readonly */ videoPlatformConf: {value: videoPlatformConf}, /** * Medias public directory path. * * @type {String} * @instance * @readonly */ mediasPublicPath: {value: path.join(process.rootPublish, 'assets/player/videos')}, /** * Package temporary directory path. * * @type {String} * @instance */ packageTemporaryDirectory: { value: path.join( publishConf.videoTmpDir, String(mediaPackage.id), mediaPackage.temporarySubDirectory || '' ), writable: true } } ); } util.inherits(Package, events.EventEmitter); module.exports = Package; /** * Package states. * * @const * @type {Object} */ Package.STATES = { PACKAGE_SUBMITTED: 'packageSubmitted', PACKAGE_INITIALIZED: 'packageInitialized', PACKAGE_COPIED: 'packageCopied', ORIGINAL_PACKAGE_REMOVED: 'originalPackageRemoved', MEDIA_UPLOADED: 'mediaUploaded', MEDIA_SYNCHRONIZED: 'mediaSynchronized', DIRECTORY_CLEANED: 'directoryCleaned', MERGE_INITIALIZED: 'mergeInitialized', MERGED: 'merged', MERGE_FINALIZED: 'mergeFinalized', PACKAGE_REMOVED: 'packageRemoved' }; Object.freeze(Package.STATES); /** * Package transitions (from one state to another). * * @const * @type {Object} */ Package.TRANSITIONS = { INIT: 'initPackage', COPY_PACKAGE: 'copyPackage', REMOVE_ORIGINAL_PACKAGE: 'removeOriginalPackage', UPLOAD_MEDIA: 'uploadMedia', SYNCHRONIZE_MEDIA: 'synchronizeMedia', CLEAN_DIRECTORY: 'cleanDirectory', INIT_MERGE: 'initMerge', MERGE: 'merge', FINALIZE_MERGE: 'finalizeMerge', REMOVE_PACKAGE: 'removePackage' }; Object.freeze(Package.TRANSITIONS); /** * Define the order in which transitions will be executed for a Package. * * @const * @type {Object} */ Package.stateTransitions = [ Package.TRANSITIONS.INIT, Package.TRANSITIONS.COPY_PACKAGE, Package.TRANSITIONS.REMOVE_ORIGINAL_PACKAGE, Package.TRANSITIONS.UPLOAD_MEDIA, Package.TRANSITIONS.SYNCHRONIZE_MEDIA, Package.TRANSITIONS.CLEAN_DIRECTORY, Package.TRANSITIONS.INIT_MERGE, Package.TRANSITIONS.MERGE, Package.TRANSITIONS.FINALIZE_MERGE, Package.TRANSITIONS.REMOVE_PACKAGE ]; Object.freeze(Package.stateTransitions); /** * Define machine state authorized transitions depending on previous and next states. * * @const * @type {Object} */ Package.stateMachine = [ { name: Package.TRANSITIONS.INIT, from: Package.STATES.PACKAGE_SUBMITTED, to: Package.STATES.PACKAGE_INITIALIZED }, { name: Package.TRANSITIONS.COPY_PACKAGE, from: Package.STATES.PACKAGE_INITIALIZED, to: Package.STATES.PACKAGE_COPIED }, { name: Package.TRANSITIONS.REMOVE_ORIGINAL_PACKAGE, from: Package.STATES.PACKAGE_COPIED, to: Package.STATES.ORIGINAL_PACKAGE_REMOVED }, { name: Package.TRANSITIONS.UPLOAD_MEDIA, from: Package.STATES.ORIGINAL_PACKAGE_REMOVED, to: Package.STATES.MEDIA_UPLOADED }, { name: Package.TRANSITIONS.SYNCHRONIZE_MEDIA, from: Package.STATES.MEDIA_UPLOADED, to: Package.STATES.MEDIA_SYNCHRONIZED }, { name: Package.TRANSITIONS.CLEAN_DIRECTORY, from: Package.STATES.MEDIA_SYNCHRONIZED, to: Package.STATES.DIRECTORY_CLEANED }, { name: Package.TRANSITIONS.INIT_MERGE, from: Package.STATES.DIRECTORY_CLEANED, to: Package.STATES.MERGE_INITIALIZED }, { name: Package.TRANSITIONS.MERGE, from: Package.STATES.MERGE_INITIALIZED, to: Package.STATES.MERGED }, { name: Package.TRANSITIONS.FINALIZE_MERGE, from: Package.STATES.MERGED, to: Package.STATES.MERGE_FINALIZED }, { name: Package.TRANSITIONS.REMOVE_PACKAGE, from: Package.STATES.MERGE_FINALIZED, to: Package.STATES.PACKAGE_REMOVED } ]; Object.freeze(Package.stateMachine); /** * Packages that have been locked by another one with the locker id as property name and the locked id as property * value. * * This object is static to offer a loop safe context for packages which might try to lock another package with the * same name at the same time. * Lock information is stored in storage but is not loop safe. * * @const * @type {Object} */ Package.lockedPackages = {}; /** * Waits for the given media to be in one of the given states. * * Waiting interval is 1 second. * * @memberof module:publish/packages/Package~Package * @this module:publish/packages/Package~Package * @private * @param {Object} media The media * @param {Object} media.id The media id * @param {Array} states The authorized states * @param {module:publish/packages/Package~Package~waitForMediaStateCallback} callback The function to call * when done */ function waitForMediaState(media, states, callback) { var self = this; this.videoProvider.getOne( new ResourceFilter().equal('id', media.id), null, function(error, fetchedMedia) { if (error) return callback(error); if (!fetchedMedia) return callback(new Error('Media "' + media.id + '" not found')); if (states.indexOf(fetchedMedia.state) !== -1) return callback(null, fetchedMedia); setTimeout(function() { waitForMediaState.call(self, media, states, callback); }, 1000); } ); } /** * Finds if a package is actually locked. * * @memberof module:publish/packages/Package~Package * @this module:publish/packages/Package~Package * @private * @param {Object} packageToTest The package to test * @param {String} packageToTest.id The package to test * @return {Boolean} true if package is locked, false otherwise */ function isLocked(packageToTest) { return Object.values(Package.lockedPackages).indexOf(packageToTest.id) !== -1; } /** * Finds locker's id of a package. * * @memberof module:publish/packages/Package~Package * @this module:publish/packages/Package~Package * @private * @param {Object} lockedPackage The locked package * @param {String} lockedPackage.id The locked package id * @return {(String|null)} The locker's id of the given package or null if package isn't locked */ function getLockerId(lockedPackage) { for (var lockerId in Package.lockedPackages) { if (Package.lockedPackages[lockerId] === lockedPackage.id) { return lockerId; } } return null; } /** * Creates a state machine to publish the package. * * @param {String} initialState Initial machine state * @param {String} initialTransition Initial machine transition */ Package.prototype.init = function(initialState, initialTransition) { var self = this; // Get the list of package stack transitions var transitions = this.getTransitions(this); // Look for the initial transition in the stack of transitions var transitionIndex = transitions.indexOf(initialTransition); var transition = transitionIndex >= 0 ? transitionIndex : 0; // Create a new final state machine this.fsm = new StateMachine({ init: initialState, transitions: this.getStateMachine() }); // Handle each enter state event to launch automatically the next // transition regarding the stack of transitions this.fsm.observe('onEnterState', function() { self.log('State = ' + self.fsm.state, 'verbose'); self.executeTransition((transitions[transition + 1]) ? transitions[++transition] : null); }); // Handle each leave state event to execute the corresponding transition this.fsm.observe('onLeaveState', function(event) { self.log('Transition = ' + event.transition, 'verbose'); // Executes function corresponding to transition if (self[event.transition]) { return self[event.transition](); } else { self.setError(new PackageError('Transition ' + event.transition + ' does not exist', ERRORS.TRANSITION), true); return false; } }); }; /** * Updates media state and sends an event to inform about state changed. * * @param {Number} id The id of the media to update * @param {String} state The state of the media * @param {module:publish/packages/Package~Package~udapteStateCallback} callback The function to call when it's done */ Package.prototype.updateState = function(id, state, callback) { var self = this; this.videoProvider.updateState(id, state, function(error, totalItems) { self.emit('stateChanged', self.mediaPackage); callback(error, totalItems); }); }; /** * Starts executing at the given transition. * * The rest of the transitions stack will be executed. * * @param {String} transition The transition to launch */ Package.prototype.executeTransition = function(transition) { var self = this; if (this.mediaPackage.removed) { this.log('Package removed'); return this.emit('complete', this.mediaPackage); } // Package is initialized // Memorize the last state and last transition of the package async.parallel([ function(callback) { self.videoProvider.updateLastState(self.mediaPackage.id, self.fsm.state, callback); }, function(callback) { self.videoProvider.updateLastTransition(self.mediaPackage.id, transition, callback); } ], function() { // If no more transition or upload transition reached without platform type // The publication is considered done if ( !transition || (transition === Package.TRANSITIONS.UPLOAD_MEDIA && !self.mediaPackage.type) || (transition === Package.TRANSITIONS.MERGE && !self.mediaPackage.mergeRequired) ) { // Package has not been uploaded yet and request a manual upload // Change package state if (transition === Package.TRANSITIONS.UPLOAD_MEDIA) { self.log('Package is waiting for manual upload'); self.updateState(self.mediaPackage.id, STATES.WAITING_FOR_UPLOAD, function() { self.emit('complete', self.mediaPackage); }); } else self.updateState(self.mediaPackage.id, STATES.READY, function() { self.emit('complete', self.mediaPackage); }); } else { // Continue by executing the next transition in the stack var result = self.fsm[transition](); if (result && typeof result.then === 'function') { result.catch((error) => { self.setError(error, transition === Package.TRANSITIONS.INIT); }); } } }); }; /** * Initializes and stores the package. * * This is a transition. * * @async * @return {Promise} Promise resolving when transition is done */ Package.prototype.initPackage = function() { this.log('Init package'); var self = this; return new Promise(function(resolve, reject) { self.mediaPackage.state = STATES.PENDING; self.mediaPackage.link = null; self.mediaPackage.mediaId = null; self.mediaPackage.errorCode = ERRORS.NO_ERROR; self.mediaPackage.properties = self.mediaPackage.properties || {}; self.mediaPackage.metadata = self.mediaPackage.metadata || {}; self.mediaPackage.lastState = Package.STATES.PACKAGE_INITIALIZED; self.mediaPackage.lastTransition = Package.TRANSITIONS.COPY_PACKAGE; if (self.mediaPackage.date === undefined) self.mediaPackage.date = Date.now(); self.videoProvider.add([self.mediaPackage], function(addError, total, addedMediaPackages) { if (addError) return reject(new PackageError(addError.message, ERRORS.SAVE_PACKAGE_DATA)); self.emit('stateChanged', self.mediaPackage); self.mediaPackage = Object.assign(self.mediaPackage, addedMediaPackages[0]); resolve(); }); }); }; /** * Copies package from its submitted directory to temporary directory. * * This is a transition. * * @async * @return {Promise} Promise resolving when transition is done */ Package.prototype.copyPackage = function() { var self = this; return new Promise(function(resolve, reject) { // Destination of the copy var destinationFilePath = path.join( self.packageTemporaryDirectory, self.mediaPackage.id + '.' + self.mediaPackage.packageType ); self.updateState(self.mediaPackage.id, STATES.COPYING, function() { // Copy package self.log('Copy ' + self.mediaPackage.originalPackagePath + ' to ' + destinationFilePath); openVeoApi.fileSystem.copy(self.mediaPackage.originalPackagePath, destinationFilePath, function(copyError) { if (copyError) reject(new PackageError(copyError.message, ERRORS.COPY)); else resolve(); }); }); }); }; /** * Removes original package. * * This is a transition. * * @async * @return {Promise} Promise resolving when transition is done */ Package.prototype.removeOriginalPackage = function() { var self = this; return new Promise(function(resolve, reject) { async.parallel([ function(callback) { // Try to remove the original package self.log('Remove original package ' + self.mediaPackage.originalPackagePath); fs.unlink(self.mediaPackage.originalPackagePath, function(error) { if (error) callback(new PackageError(error.message, ERRORS.UNLINK)); else callback(); }); } ], function(error) { if (error) return reject(error); else resolve(); }); }); }; /** * Uploads the media to the media platform. * * This is a transition. * * @async * @return {Promise} Promise resolving when transition is done */ Package.prototype.uploadMedia = function() { var self = this; var mediaFilePath; return new Promise(function(resolve, reject) { async.series([ // Update package state function(callback) { self.updateState(self.mediaPackage.id, STATES.UPLOADING, callback); }, // Get media file name function(callback) { if (self.mediaPackage.mediaId && self.mediaPackage.mediaId.length) return callback(); self.getMediaFilePath(function(error, filePath) { if (error) return callback(new PackageError(error.message, ERRORS.UPLOAD_GET_MEDIA_FILE_PATH)); mediaFilePath = filePath; callback(); }); }, // Upload media function(callback) { if (self.mediaPackage.mediaId && self.mediaPackage.mediaId.length) return callback(); // Get media plaform provider from package type var mediaPlatformProvider = mediaPlatformFactory.get( self.mediaPackage.type, self.videoPlatformConf[self.mediaPackage.type] ); // Start uploading the media to the platform self.log('Upload media ' + mediaFilePath); mediaPlatformProvider.upload(mediaFilePath, function(error, id) { if (error) return callback(new PackageError(error.message, ERRORS.MEDIA_UPLOAD)); self.mediaPackage.link = '/publish/video/' + self.mediaPackage.id; self.mediaPackage.mediaId = [id]; callback(); }); }, // Update package function(callback) { self.videoProvider.updateOne( new ResourceFilter().equal('id', self.mediaPackage.id), { link: self.mediaPackage.link, mediaId: self.mediaPackage.mediaId }, function(error) { if (error) return callback(new PackageError(error.message, ERRORS.UPLOAD_MEDIA_UPDATE_PACKAGE)); callback(); } ); } ], function(error) { if (error) reject(error); else resolve(); }); }); }; /** * Synchronizes uploaded media information with the media platform. * * This is a transition. * * @async * @return {Promise} Promise resolving when transition is done */ Package.prototype.synchronizeMedia = function() { var self = this; return new Promise(function(resolve, reject) { self.log('Synchronize media'); self.updateState(self.mediaPackage.id, STATES.SYNCHRONIZING, function() { // Get media plaform provider from package type var mediaPlatformProvider = mediaPlatformFactory.get(self.mediaPackage.type, self.videoPlatformConf[self.mediaPackage.type]); // Synchronize media mediaPlatformProvider.update(self.mediaPackage, self.mediaPackage, true, function(error) { if (error) reject(new PackageError(error.message, ERRORS.MEDIA_SYNCHRONIZE)); else resolve(); }); }); }); }; /** * Removes temporary directory. * * This is a transition. * * @async * @return {Promise} Promise resolving when transition is done */ Package.prototype.cleanDirectory = function() { var self = this; return new Promise(function(resolve, reject) { async.series([ // Update package state function(callback) { self.updateState(self.mediaPackage.id, STATES.CLEANING, callback); }, // Remove temporary directory function(callback) { var directoryToRemove = self.packageTemporaryDirectory.replace( new RegExp('(.*)/' + self.mediaPackage.temporarySubDirectory), '$1' ); self.log('Remove temporary directory ' + directoryToRemove); openVeoApi.fileSystem.rmdir(directoryToRemove, function(error) { if (error) return callback(new PackageError(error.message, ERRORS.CLEAN_DIRECTORY)); callback(); }); } ], function(error) { if (error) reject(error); else resolve(); }); }); }; /** * Initializes merge if a package is found with the same name. * * This is a transition. * * @async * @return {Promise} Promise resolving when transition is done */ Package.prototype.initMerge = function() { var self = this; return new Promise(function(resolve, reject) { var packageToLock; async.series([ // Change media state to INITIALIZING_MERGE function(callback) { self.updateState(self.mediaPackage.id, STATES.INITIALIZING_MERGE, callback); }, // Try to find another package with the same name function(callback) { self.videoProvider.getAll( new ResourceFilter().equal('originalFileName', self.mediaPackage.originalFileName), null, {date: 'desc'}, function(error, packagesWithSameName) { if (error) return callback(new PackageError(error.message, ERRORS.INIT_MERGE_GET_PACKAGES_WITH_SAME_NAME)); // If this package has been locked then skip merge as only one package with the same name can be merged // (and so locked) at a time // If one candidate package has been locked then wait for this package to be ready // If no candidate package has been locked then choose either package in READY / PUBLISHED state or the // older one and lock it var defaultPackage; var lockedPackage; var readyPackage; var skipMerge = false; packagesWithSameName.forEach(function(packageWithSameName) { var isPackageWithSameNameLocked = isLocked(packageWithSameName) || packageWithSameName.lockedByPackage; if (isPackageWithSameNameLocked && packageWithSameName.id === self.mediaPackage.id) { // Current package has been locked by another package skipMerge = true; } else if (isPackageWithSameNameLocked) { // Another package has been locked either by this package or another lockedPackage = packageWithSameName; } else if (packageWithSameName.state === STATES.READY || packageWithSameName.state === STATES.PUBLISHED) { // A package is in READY / PUBLISHED state readyPackage = packageWithSameName; } else if (!defaultPackage && packageWithSameName.id !== self.mediaPackage.id) { // Other package with same name is not locked nor in READY / PUBLISHED state // Choose it as the default package to lock defaultPackage = packageWithSameName; } }); packageToLock = lockedPackage || readyPackage || defaultPackage; self.mediaPackage.mergeRequired = !skipMerge && packageToLock !== undefined; if (self.mediaPackage.mergeRequired) { // Lock package if not already locked self.lockPackage(packageToLock); } callback(); } ); }, // Set package as package to be merged function(callback) { if (!self.mediaPackage.mergeRequired) return callback(); self.videoProvider.updateOne( new ResourceFilter().equal('id', self.mediaPackage.id), { mergeRequired: true }, function(error, totalItems) { if (error) { return callback(new PackageError(error.message, ERRORS.INIT_MERGE_UPDATE_PACKAGE)); } callback(); } ); }, // Wait for the other package to be in READY or PUBLISHED state (so unlocked) function(callback) { if (!self.mediaPackage.mergeRequired) return callback(); var expectedStates = [STATES.READY, STATES.PUBLISHED]; var waitForPackage = function(waitForPackageCallback) { if (isLocked(packageToLock) && getLockerId(packageToLock) !== self.mediaPackage.id) { // Package has already been locked by another one // Wait again for the package to be released self.log( 'Package ' + packageToLock.id + ' already locked by package ' + getLockerId(packageToLock), 'verbose' ); setTimeout(function() { waitForPackage(waitForPackageCallback); }, 1000); return; } // Wait for other package to be in READY / PUBLISHED state waitForMediaState.call(self, packageToLock, expectedStates, function(error) { if (error) { return waitForPackageCallback(new PackageError(error.message, ERRORS.INIT_MERGE_WAIT_FOR_MEDIA)); } if (isLocked(packageToLock) && getLockerId(packageToLock) !== self.mediaPackage.id) { // Package has already been locked by another one thus its state is going to change soon // Wait again for the package to be released and in READY / PUBLISHED state self.log( 'Package ' + packageToLock.id + ' ready but locked by package ' + getLockerId(packageToLock), 'verbose' ); waitForPackage(waitForPackageCallback); } else { // Package has not been locked by any other package // Lock it self.lockPackage(packageToLock); waitForPackageCallback(); } }); }; if ( expectedStates.indexOf(packageToLock.state) !== -1 && getLockerId(packageToLock) === self.mediaPackage.id ) { // Package is already in READY / PUBLISHED state and package has been locked by current package return callback(); } else { self.log('Wait for package ' + packageToLock.id + ' to be ready'); waitForPackage(callback); } }, // Lock other package now that it has been released function(callback) { if (!self.mediaPackage.mergeRequired) return callback(); self.log('Change package ' + packageToLock.id + ' state to WAITING_FOR_MERGE'); self.videoProvider.updateOne( new ResourceFilter().equal('id', packageToLock.id), { lockedByPackage: self.mediaPackage.id, state: STATES.WAITING_FOR_MERGE }, function(error, totalItems) { if (error) { return callback(new PackageError(error.message, ERRORS.INIT_MERGE_LOCK_PACKAGE)); } callback(); } ); } ], function(error) { if (error) return reject(error); resolve(); }); }); }; /** * Merges package with the one with the same name. * * This is a transition. * * @async * @return {Promise} Promise resolving when transition is done */ Package.prototype.merge = function() { var self = this; return new Promise(function(resolve, reject) { self.updateState(self.mediaPackage.id, STATES.MERGING, function(error) { if (error) return reject(error); resolve(); }); }); }; /** * Finalizes merge by resetting state of the locked package of the same name. * * This is a transition. * * @async * @return {Promise} Promise resolving when transition is done */ Package.prototype.finalizeMerge = function() { var self = this; return new Promise(function(resolve, reject) { var lockedPackage; async.series([ // Change media state to FINALIZING_MERGE function(callback) { self.updateState(self.mediaPackage.id, STATES.FINALIZING_MERGE, callback); }, // Find package locked in WAITING_FOR_MERGE state function(callback) { self.videoProvider.getOne( new ResourceFilter().and([ new ResourceFilter().equal('state', STATES.WAITING_FOR_MERGE), new ResourceFilter().equal('originalFileName', self.mediaPackage.originalFileName), new ResourceFilter().equal('lockedByPackage', self.mediaPackage.id) ]), null, function(error, foundPackage) { if (error) { return callback(new PackageError(error.message, ERRORS.FINALIZE_MERGE_GET_PACKAGE_WITH_SAME_NAME)); } lockedPackage = foundPackage; callback(); } ); }, // Reset locked package state and release lock // Not that state is reset to READY even if the state was previously PUBLISHED because it requires manual check // by the user before publishing the package function(callback) { self.videoProvider.updateOne( new ResourceFilter().equal('id', lockedPackage.id), { lockedByPackage: null, state: STATES.READY }, function(error, totalItems) { if (error) { return callback(new PackageError(error.message, ERRORS.FINALIZE_MERGE_RELEASE_PACKAGE)); } self.log('Release lock on package ' + Package.lockedPackages[self.mediaPackage.id], 'verbose'); delete Package.lockedPackages[self.mediaPackage.id]; callback(); } ); } ], function(error) { if (error) return reject(error); resolve(); }); }); }; /** * Removes the package from OpenVeo but not from the media platform. * * This is a transition. * * @async * @return {Promise} Promise resolving when transition is done */ Package.prototype.removePackage = function() { var self = this; return new Promise(function(resolve, reject) { async.series([ // Change media state to REMOVING function(callback) { self.updateState(self.mediaPackage.id, STATES.REMOVING, callback); }, // Remove package from OpenVeo function(callback) { self.videoProvider.removeLocal(new ResourceFilter().equal('id', self.mediaPackage.id), function(error) { if (error) return callback(new PackageError(error.message, ERRORS.REMOVE_PACKAGE)); self.mediaPackage.removed = true; callback(); }); } ], function(error) { if (error) return reject(error); resolve(); }); }); }; /** * Gets the stack of transitions corresponding to the package. * * Each package has its own way to be published, thus transitions stack * is different by package. * * @return {Array} The stack of transitions */ Package.prototype.getTransitions = function() { return Package.stateTransitions; }; /** * Gets the list of transitions states corresponding to the package. * * @return {Array} The list of states/transitions */ Package.prototype.getStateMachine = function() { return Package.stateMachine; }; /** * Gets the media file path of the package. * * @return {module:publish/packages/Package~Package~getMediaFilePathCallback} Function to call when its done */ Package.prototype.getMediaFilePath = function(callback) { callback(null, path.join( this.packageTemporaryDirectory, this.mediaPackage.id + '.' + this.mediaPackage.packageType )); }; /** * Locks given package if not already locked. * * @param {Object} packageToLock The package to lock */ Package.prototype.lockPackage = function(packageToLock) { if (isLocked(packageToLock)) return; this.log('Lock package ' + packageToLock.id, 'verbose'); Package.lockedPackages[this.mediaPackage.id] = packageToLock.id; }; /** * Logs given message suffixing it with the package id. * * @param {String} message The message to log * @param {String} [level="debug"] The expected log level */ Package.prototype.log = function(message, level) { process.logger[level || 'debug'](message, {id: this.mediaPackage.id}); }; /** * Sets a package as in error. * * @param {module:publish/PublishError~PublishError} error The package error * @param {boolean} doNotUpdateMedia true to simply emit the error without updating the media */ Package.prototype.setError = function(error, doNotUpdateMedia) { var self = this; // An error occurred if (error) { if (Package.lockedPackages[this.mediaPackage.id]) { this.log('Release lock on package ' + Package.lockedPackages[this.mediaPackage.id], 'verbose'); delete Package.lockedPackages[this.mediaPackage.id]; } async.parallel([ function(callback) { if (doNotUpdateMedia) return callback(); self.updateState(self.mediaPackage.id, STATES.ERROR, callback); }, function(callback) { if (doNotUpdateMedia) return callback(); self.videoProvider.updateErrorCode(self.mediaPackage.id, error.code, callback); } ], function() { self.emit('error', error); }); } }; /** * @callback module:publish/packages/Package~Package~getMediaFilePathCallback * @param {(Error|undefined)} error The error if an error occurred * @param {String} mediaFilePath The path of the media file in temporary directory */ /** * @callback module:publish/packages/Package~Package~udapteStateCallback * @param {(Error|undefined)} error The error if an error occurred * @param {Number} total The number of udpdated items */ /** * @callback module:publish/packages/Package~Package~waitForMediaStateCallback * @param {(Error|undefined)} error The error if an error occurred * @param {Object} media The media with all properties */