API Docs for: 0.8.0
Show:

File: include/service/entities/media_service.js

/*
	Copyright (C) 2016  PencilBlue, LLC

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

//dependencies
var http  = require('http');
var fs    = require('fs');
var path  = require('path');
var async = require('async');
var util  = require('../../util.js');

module.exports = function MediaServiceModule(pb) {

    /**
     * Provides information on media
     * @deprecated
     * @class MediaService
     * @constructor
     * @param {MediaProvider} provider
     * @param {string} site Site uid to be used for this service
     * @param {boolean} onlyThisSite Whether this service should only return media associated with specified site
     * or fallback to global if not found in specified site
     */
    function MediaService(provider, site, onlyThisSite) {
        pb.log.warn('MediaService is deprecated. Please use MediaServiceV2');

        /**
         * @property site
         * @type {String}
         */
        this.site = pb.SiteService.getCurrentSite(site);

        var context = {
            site: this.site,
            onlyThisSite: onlyThisSite
        };

        /**
         * @property siteQueryService
         * @type {SiteQueryService}
         */
        this.siteQueryService = new pb.SiteQueryService(context);

        if (util.isNullOrUndefined(provider)) {
            provider = pb.MediaServiceV2.loadMediaProvider(context);
        }
        if (!provider) {
            throw new Error('A valid media provider is required. Please check your configuration');
        }

        /**
         * @property provider
         * @type {MediaProvider}
         */
        this.provider = provider;
    }

    /**
     * The collection where media descriptors are persisted
     * @static
     * @readonly
     * @property COLL
     * @type {String}
     */
    MediaService.COLL = 'media';

    /**
     * Loads a media descriptor by ID.
     * @method loadById
     * @param {String|ObjectID} mid Media descriptor ID
     * @param {Function} cb A callback that provides two parameters: an Error, if
     * occurred and a media descriptor if found.
     */
    MediaService.prototype.loadById = function(mid, cb) {
        this.siteQueryService.loadById(mid.toString(), MediaService.COLL, cb);
    };

    /**
     * Deletes a media descriptor by ID
     * @method deleteById
     * @param {String|ObjectID} mid
     * @param {Object} [options] not used in implementation
     * @param {Function} cb
     */
    MediaService.prototype.deleteById = function(mid, options, cb) {
        if (util.isFunction(options)) {
            cb     = options;
            options = {delete_content: true};
        }

        var self = this;
        self.siteQueryService.deleteById(mid, MediaService.COLL, cb);
    };

    /**
     * Persists a media descriptor
     * @method save
     * @param {Object} media
     * @param {Object} [options] Options object is not actually used in this implementation
     * @param {Function} cb
     */
    MediaService.prototype.save = function(media, options, cb) {
        if (util.isFunction(options)) {
            cb      = options;
            options = {};
        }

        var self = this;
        self.validate(media, function(err, validationErrors) {
            if (util.isError(err)) {
                return cb(err);
            }
            else if (validationErrors.length) {
                return cb(null, validationErrors);
            }

            self.siteQueryService.save(media, cb);
        });
    };

    /**
     * Validates a media descriptor
     * @method validate
     * @param {Object} media
     * @param {Function} cb A callback that provides two parameters: an Error, if
     * occurred.  The second is an array of validation error objects.
     */
    MediaService.prototype.validate = function(media, cb) {
        var errors = [];

        if (!util.isObject(media)) {
            errors.push(pb.CustomObjectService.err('', 'The descriptor must be an object'));
            return cb(null, errors);
        }

        //ensure the media name is unique
        var where = { name: media.name };
        this.siteQueryService.unique(MediaService.COLL, where, media[pb.DAO.getIdField()], function(err, isUnique) {
            if(util.isError(err)) {
                return cb(err, errors);
            }
            else if(!isUnique) {
                errors.push(pb.CustomObjectService.err('name', 'The name ' + media.name + ' is already in use'));
            }

            //TODO validate the other properties
            cb(null, errors);
        });
    };

    /**
     * Queries for media descriptors
     * @method get
     * @param {Object} [options]
     * @param {Object} [options.where]
     * @param {Array} [options.order]
     * @param {Object} [options.select]
     * @param {Integer} [options.limit]
     * @param {Integer} [options.offset]
     * @param {Boolean} [options.format_media=true]
     * @param cb
     */
    MediaService.prototype.get = function(options, cb) {
        if (util.isFunction(options)) {
            cb      = options;
            options = {
                format_media: true,
                order: [['name', 1]]
            };
        }

        this.siteQueryService.q('media', options, function (err, media) {
            if (util.isError(err)) {
                return cb(err, []);
            }

            //set the link and icon if specified
            if (options.format_media) {
                pb.MediaServiceV2.formatMedia(media);
            }
            cb(null, media);
        });
    };

    /**
     *
     * @method getContentByPath
     * @param {String} mediaPath
     * @param {Function} cb
     */
    MediaService.prototype.getContentByPath = function(mediaPath, cb) {
        this.provider.get(mediaPath, cb);
    };

    /**
     *
     * @method getContentStreamByPath
     * @param {String} mediaPath
     * @param {Function} cb
     */
    MediaService.prototype.getContentStreamByPath = function(mediaPath, cb) {
        this.provider.getStream(mediaPath, cb);
    };

    /**
     * @method getContentStreamById
     * @param {string} id
     * @param {Function} cb
     */
    MediaService.prototype.getContentStreamById = function(id, cb) {
        var self = this;
        var tasks = [
            function(callback) { self.siteQueryService.loadById(id, MediaService.COLL, callback); },
            function(media, callback) {
                if (!media) {
                    return callback(null, null);
                }
                self.provider.getStream(media.location, function(err, stream) {
                    callback(err, stream ? {
                        stream: stream,
                        mime: pb.RequestHandler.getMimeFromPath(this.req.url)
                    } : null);
                });
            }
        ];
        async.waterfall(tasks, cb);
    };

    /**
     *
     * @method setContent
     * @param {String|Buffer} fileDataStrOrBuff
     * @param {String} fileName
     * @param {Function} cb
     */
    MediaService.prototype.setContent = function(fileDataStrOrBuff, fileName, cb) {
        var mediaPath = pb.MediaServiceV2.generateMediaPath(fileName);
        this.provider.set(fileDataStrOrBuff, mediaPath, function(err, result) {
            cb(err, { mediaPath: mediaPath, result: result});
        });
    };

    /**
     *
     * @method setContentStream
     * @param {Stream} stream
     * @param {String} fileName
     * @param {Function} cb
     */
    MediaService.prototype.setContentStream = function(stream, fileName, cb) {
        var mediaPath = pb.MediaServiceV2.generateMediaPath(fileName);
        this.provider.setStream(stream, mediaPath, function(err, result) {
            cb(err, { mediaPath: mediaPath, result: result});
        });
    };

    /**
     *
     * @method createContentWriteStream
     * @param {String} fileName
     * @param {Function} cb
     */
    MediaService.prototype.createContentWriteStream = function(fileName, cb) {
        var mediaPath = pb.MediaServiceV2.generateMediaPath(fileName);
        this.provider.createWriteStream(mediaPath, function(err, stream) {
            var result = {
                mediaPath: mediaPath,
                stream: stream
            };
            cb(err, result);
        });
    };

    /**
     *
     * @method existsByPath
     * @param {String} mediaPath
     * @param {Function} cb
     */
    MediaService.prototype.existsByPath = function(mediaPath, cb) {
        this.provider.exists(mediaPath, cb);
    };

    /**
     *
     * @method deleteContentByPath
     * @param {String} mediaPath
     * @param {Function} cb
     */
    MediaService.prototype.deleteContentByPath = function(mediaPath, cb) {
        this.provider.delete(mediaPath, cb);
    };

    /**
     *
     * @method statByPath
     * @param {String} mediaPath
     * @param {Function} cb
     */
    MediaService.prototype.statByPath = function(mediaPath, cb) {
        this.provider.stat(mediaPath, cb);
    };

    /**
     * Retrieves whether a media's file path is valid
     *
     * @method isValidFilePath
     * @param {String}   mediaPath The file path of the media
     * @param {Function} cb        Callback function
     */
    MediaService.prototype.isValidFilePath = function(mediaPath, cb) {
        var absolutePath = path.join(pb.config.docRoot, 'public', mediaPath);
        fs.exists(absolutePath, function(exists) {
            cb(null, exists);
        });
    };

    /**
     * Registers a media renderer
     * @deprecated
     * @static
     * @method registerRenderer
     * @param {Function|Object} interfaceImplementation A prototype or object that implements the media renderer interface.
     * @return {Boolean} TRUE if the implementation was registered, FALSE if not
     */
    MediaService.registerRenderer = function(interfaceImplementation) {
        pb.log.warn('MediaService: registerRenderer is deprecated. Use MediaServiceV2.registerRenderer');
        return pb.MediaServiceV2.registerRenderer(interfaceImplementation);
    };

    /**
     * Indicates if a media renderer is already registered
     * @deprecated
     * @static
     * @method isRegistered
     * @param {Function|Object} interfaceImplementation A prototype or object that implements the media renderer interface
     * @return {Boolean} TRUE if registered, FALSE if not
     */
    MediaService.isRegistered = function(interfaceImplementation) {
        pb.log.warn('MediaService: isRegistered is deprecated. Use MediaServiceV2.isRegistered');
        return pb.MediaServiceV2.isRegistered(interfaceImplementation);
    };

    /**
     * Unregisters a media renderer
     * @deprecated
     * @static
     * @method unregisterRenderer
     * @param {Function|Object} interfaceToUnregister A prototype or object that implements the media renderer interface
     * @return {Boolean} TRUE if unregistered, FALSE if not
     */
    MediaService.unregisterRenderer = function(interfaceToUnregister) {
        pb.log.warn('MediaService: unregisterRenderer is deprecated. Use MediaServiceV2.unregisterRenderer');
        return pb.MediaServiceV2.unregisterRenderer(interfaceToUnregister);
    };

    /**
     * Determines if the media URI is a file.  It is determined to be a file if and
     * only if the URI does not begin with "http" or "//".
     * @deprecated
     * @static
     * @method isFile
     * @param {String} mediaUrl A URI string that points to a media resource
     */
    MediaService.isFile = function(mediaUrl) {
        pb.log.warn('MediaService: isFile is deprecated. Use MediaServiceV2.isFile instead');
        return mediaUrl.indexOf('http') !== 0 && mediaUrl.indexOf('//') !== 0;
    };

    /**
     * Generates a media descriptor for a given media URL
     * @method getMediaDescriptor
     * @param {String} mediaUrl
     * @param {Function} cb A callback with two parameters. First, an Error if
     * occurred and second is an object that describes the media resource described
     * by the given media URL
     */
    MediaService.prototype.getMediaDescriptor = function(mediaUrl, cb) {

        //get the type
        var isFile = mediaUrl.indexOf('http') !== 0 && mediaUrl.indexOf('//') !== 0;
        var result = pb.MediaServiceV2.getRenderer(mediaUrl, isFile);
        if (result === null) {
            pb.log.warn('MediaService: No media renderer could be found for URI [%s]', mediaUrl);
            return cb(null, null);
        }

        var renderer = result.renderer;
        var tasks = {

            meta: function(callback) {
                renderer.getMeta(mediaUrl, isFile, callback);
            },

            thumbnail: function(callback) {
                renderer.getThumbnail(mediaUrl, callback);
            },

            mediaId: function(callback) {
                renderer.getMediaId(mediaUrl, callback);
            }
        };
        async.series(tasks, function(err, taskResult) {
            var descriptor = {
                type: result.type,
                media_type: result.type,
                isFile: isFile,
                is_file: isFile,
                url: mediaUrl,
                icon: renderer.getIcon(result.type),
                thumbnail: taskResult.thumbnail,
                location: taskResult.mediaId,
                meta: taskResult.meta
            };
            cb(err, descriptor);
        });
    };

    /**
     * Renders a resource by type and location (mediaId).
     * @method renderByLocation
     * @param {Object} options
     * @param {String} options.location The unique media identifier for the type
     * @param {String} [options.type] The type of provider that knows how to render
     * the resource
     * @param {Object} [options.attrs] The desired HTML attributes that will be
     * added to the element that provides the rendering
     * @param {Object} [options.style] The desired style overrides for the media
     * @param {String} [options.view] The view type that the media will be rendered
     * for (view, editor, post).  Any style options provided will override those
     * provided by the default style associated with the view.
     * @param {boolean} [options.isFile=false]
     * @param {Function} cb A callback that provides two parameters.  An Error if
     * exists and the rendered HTML content for the media resource.
     */
    MediaService.prototype.renderByLocation = function(options, cb) {
        var result = options.type ? pb.MediaServiceV2.getRendererByType(options.type) : pb.MediaServiceV2.getRenderer(options.location, options.isFile);
        if (!result) {
            var failures = [ BaseObjectService.validationFailure('type', 'An invalid type was provided') ];
            var err = BaseObjectService.validationError(failures);
            return cb(err, null);
        }

        //set style properties if we can
        if (options.view) {
            options.style = pb.MediaServiceV2.getStyleForView(result.renderer, options.view, options.style);
        }

        result.renderer.render(options, options, cb);
    };

    /**
     * Renders a media resource by ID where ID refers the to the media descriptor
     * id.
     * @method renderById
     * @param {String} id The media resource ID
     * @param {Object} options
     * @param {Object} [options.attrs] The desired HTML attributes that will be
     * added to the element that provides the rendering
     * @param {Object} [options.style] The desired style overrides for the media
     * @param {String} [options.view] The view type that the media will be rendered
     * for (view, editor, post).  Any style options provided will override those
     * provided by the default style associated with the view.
     * @param {Function} cb A callback that provides two parameters. An Error if
     * exists and the rendered HTML content for the media resource.
     */
    MediaService.prototype.renderById = function(id, options, cb) {
        var self = this;

        self.siteQueryService.loadById(id, MediaService.COLL, function (err, media) {
            if (util.isError(err) || !media) {
                return cb(err, null);
            }

            //render
            self.render(media, options, cb);
        });
    };

    /**
     * Renders the media represented by the provided media flag
     * @method render
     * @param {String|Object} flag The media resource ID
     * @param {Object} options
     * @param {Object} [options.attrs] The desired HTML attributes that will be
     * added to the element that provides the rendering
     * @param {Object} [options.style] The desired style overrides for the media
     * @param {String} [options.view] The view type that the media will be rendered
     * for (view, editor, post).  Any style options provided will override those
     * provided by the default style associated with the view.
     * @param {Function} cb A callback that provides two parameters. An Error if
     * exists and the rendered HTML content for the media resource.
     */
    MediaService.prototype.renderByFlag = function(flag, options, cb) {
        if (util.isString(flag)) {
            flag = pb.MediaServiceV2.parseMediaFlag(flag);
        }
        if (!flag) {
            return cb(new Error('An invalid flag was passed'));
        }

        //check for absense of props
        if (util.isFunction(options)) {
            cb = options;
            options = {
                style: {}
            };
        }

        //ensure the max height is set if explicity set for media replacement
        if (flag.style.maxHeight) {
            options.style['max-height'] = flag.style.maxHeight;
        }
        this.renderById(flag.id, options, cb);
    };

    /**
     * Renders the media represented by the provided media descriptor.
     * @method render
     * @param {String} The media resource ID
     * @param {Object} options
     * @param {Object} [options.attrs] The desired HTML attributes that will be
     * added to the element that provides the rendering
     * @param {Object} [options.style] The desired style overrides for the media
     * @param {String} [options.view] The view type that the media will be rendered
     * for (view, editor, post).  Any style options provided will override those
     * provided by the default style associated with the view.
     * @param {Function} cb A callback that provides two parameters. An Error if
     * exists and the rendered HTML content for the media resource.
     */
    MediaService.prototype.render = function(media, options, cb) {
        //retrieve renderer
        var result = pb.MediaServiceV2.getRendererByType(media.media_type);
        if (!result) {
            return cb(null, null);
        }

        //set style properties if we can
        if (options.view) {
            options.style = pb.MediaServiceV2.getStyleForView(result.renderer, options.view, options.style);
        }

        //render media
        result.renderer.render(media, options, cb);
    };

    /**
     * Retrieves the base style for the given renderer and view.  Overrides will be
     * applied on top of the base style.
     * @deprecated
     * @static
     * @method getStyleForView
     * @param {MediaRenderer} renderer An implementation of MediaRenderer
     * @param {String} view The view to retrieve the default styling for (view,
     * editor, post)
     * @param {Object} [overrides] A hash of style properties that will be applied
     * to the base style for the given view
     */
    MediaService.getStyleForView = function(renderer, view, overrides) {
        pb.log.warn('MediaService: getStyleForView is deprecated. Use MediaServiceV2.getStyleForView');
        return pb.MediaServiceV2.getStyleForView(renderer, view, overrides);
    };

    /**
     * Retrieves a media renderer for the specified type
     * @deprecated
     * @static
     * @method getRendererByType
     * @param {String} type The media type
     * @return {MediaRenderer} A media renderer interface implementation or NULL if
     * none support the given type.
     */
    MediaService.getRendererByType = function(type) {
        pb.log.warn('MediaService: getRendererByType is deprecated. Use MediaServiceV2.getRendererByType');
        return pb.MediaServiceV2.getRendererByType(type);
    };

    /**
     * Retrieves a media renderer for the specified URL
     * @deprecated
     * @static
     * @method getRendererByType
     * @param {String} mediaUrl The media URL
     * @param {Boolean} isFile TRUE if the URL represents an uploaded file, FALSE if not
     * @return {MediaRenderer} A media renderer interface implementation or NULL if
     * none support the given URL.
     */
    MediaService.getRenderer = function(mediaUrl, isFile) {
        pb.log.warn('MediaService: getRenderer is deprecated.  Use MediaServiceV2.getRenderer');
        return pb.MediaServiceV2.getRenderer(mediaUrl, isFile);
    };

    /**
     * Generates a media placeholder for templating
     * @deprecated
     * @static
     * @method getMediaFlag
     * @param {String} mid The media descriptor ID
     * @param {Object} [options] The list of attributes to be provided to the
     * rendering element.
     * @return {String}
     */
    MediaService.getMediaFlag = function(mid, options) {
        pb.log.warn('MediaService: getMediaFlag is deprecated.  Use MediaServiceV2.getMediaFlag');
        return pb.MediaServiceV2.getMediaFlag(mid, options);
    };

    /**
     * Given a content string the function will search for and extract the first
     * occurrence of a media flag. The parsed value that is returned will include:
     * <ul>
     * <li>startIndex - The index where the flag was found to start</li>
     * <li>endIndex - The position in the content string of the last character of the media flag</li>
     * <li>flag - The entire media flag including the start and end markers</li>
     * <li>id - The media descriptor id that is referenced by the media flag</li>
     * <li>style - A hash of the style properties declared for the flag</li>
     * <li>cleanFlag - The media flag stripped of the start and end markers</li>
     * </ul>
     * @deprecated
     * @static
     * @method extractNextMediaFlag
     * @param {String} content The content string that potentially contains 1 or more media flags
     * @return {Object} An object that contains the information about the parsed media flag.
     */
    MediaService.extractNextMediaFlag = function(content) {
        pb.log.warn('MediaService: extractNextMediaFlag is deprecated.  Use MediaServiceV2.extractNextMediaFlag');
        return pb.MediaServiceV2.extractNextMediaFlag(content);
    };

    /**
     * Parses a media flag and returns each part in an object. The parsed value that
     * is returned will include:
     * <ul>
     * <li>id - The media descriptor id that is referenced by the media flag</li>
     * <li>style - A hash of the style properties declared for the flag</li>
     * <li>cleanFlag - The media flag stripped of the start and end markers</li>
     * </ul>
     * @static
     * @method .
     * @param {String} content The content string that potentially contains 1 or more media flags
     * @return {Object} An object that contains the information about the parsed media flag.
     */
    MediaService.parseMediaFlag = function(flag) {
        pb.log.warn('MediaService: parseMediaFlag is deprecated. Use MediaServiceV2.parseMediaFlag');
        return pb.MediaServiceV2.parseMediaFlag(flag);
    };

    /**
     * The default editor implementations all for three position values to declared
     * for embeded media (none, left, right, center).  These values map to HTML
     * alignments.  This function retrieves the HTML style attribute for the
     * provided position.
     * @deprecated
     * @static
     * @method getStyleForPosition
     * @param {String} position Can be one of 4 values: none, left, right, center
     * @return {String} The HTML formatted style attribute(s)
     */
    MediaService.getStyleForPosition = function(position) {
        pb.log.warn('MediaService: getStyleForPosition is deprecated. Use MediaServiceV2.getStyleForPosition');
        return pb.MediaServiceV2.getStyleForPosition(position);
    };

    /**
     * Generates the path to uploaded media
     * @deprecated
     * @static
     * @method generateMediaPath
     * @param {String} originalFilename
     * @return {String}
     */
    MediaService.generateMediaPath = function(originalFilename) {
        pb.log.warn('MediaService: generateMediaPath is deprecated. Use MediaServiceV2.generateMediaPath');
        return pb.MediaServiceV2.generateMediaPath(originalFilename);
    };

    /**
     * Generates a filename for a new media object
     * @deprecated
     * @static
     * @method generateFilename
     * @param {String} originalFilename
     * @return {String}
     */
    MediaService.generateFilename = function(originalFilename){
        pb.log.warn('MediaService: generateFilename is deprecated. Use MediaServiceV2.generateFilename');
        return pb.MediaServiceV2.generateFilename(originalFilename);
    };

    /**
     * Retrieves the font awesome icon for the media type.
     * @deprecated
     * @static
     * @method getMediaIcon
     * @param {String} mediaType
     * @return {String}
     */
    MediaService.getMediaIcon = function(mediaType) {
        pb.log.warn('MediaService: getMediaIcon is deprecated. Use MediaServiceV2.getMediaIcon');
        return pb.MediaServiceV2.getMediaIcon(mediaType);
    };

    /**
     * Sets the proper icon and link for an array of media items
     * @deprecated
     * @static
     * @method formatMedia
     * @param {Array} media The array of media objects to format
     * @return {Array} The same array of media that was passed in
     */
    MediaService.formatMedia = function(media) {
        pb.log.warn('MediaService: formatMedia is deprecated. Use MediaServiceV2.formatMedia');
        return pb.MediaServiceV2.formatMedia(media);
    };

    /**
     * Provides a mechanism to retrieve all of the supported extension types
     * that can be uploaded into the system.
     * @deprecated
     * @static
     * @method getSupportedExtensions
     * @return {Array} provides an array of strings
     */
    MediaService.getSupportedExtensions = function() {
        pb.log.warn('MediaService: getSupportedExtensions is deprecated. Use MediaServiceV2.getSupportedExtensions');
        return pb.MediaServiceV2.getSupportedExtensions();
    };

    /**
     * @deprecated
     * @static
     * @method loadMediaProvider
     * @param {Object} context
     * @param {String} context.site
     * @return {MediaProvider} An instance of a media provider or NULL when no
     * provider can be loaded.
     */
    MediaService.loadMediaProvider = function(context) {
        pb.log.warn('MediaService: loadMediaProvider is deprecated. Use MediaServiceV2.loadMediaProvider');
        return pb.MediaServiceV2.loadMediaProvider(context);
    };

    /**
     * Looks up the prototype for the media provider based on the configuration
     * @deprecated
     * @static
     * @method findProviderType
     * @return {MediaProvider}
     */
    MediaService.findProviderType = function() {
        pb.log.warn('MediaService: findProviderType is deprecated. Use MediaServiceV2.findProviderType');
        return pb.MediaServiceV2.findProviderType();
    };

    //exports
    return MediaService;
};