emiloberg/node-red-contrib-mopidy

View on GitHub
src/lib/models/MopidyServer.js

Summary

Maintainability
A
0 mins
Test Coverage
'use strict';

var Promise = require('promise');
var Mopidy = require('mopidy');
import {EventEmitter} from 'events';
import log from '../utils/log';
import {snakeToCamel, convertToInt, cutCore} from '../utils/utils';
var objectPath = require('object-path');

export default class MopidyServer {
    constructor({ host, port, serverId, name = '' }) {
        this._host = host;
        this._port = port;
        this._name = name;
        this._id = serverId;
        this.events = new EventEmitter();
        this._mopidy = new Mopidy({
            webSocketUrl:      `ws://${host}:${port}/mopidy/ws/`,
            callingConvention: 'by-position-or-by-name',
            console:           {
                warn: log.debug
            }
        });

        this.events.setMaxListeners(0);

        this._mopidy.on('state:online', () => {
            this._mopidy._send({method: 'core.describe'})
            .then(data => {
                this.mopidyApi = data;
                this.events.emit('ready:ready');
            });
        });

    }

    invokeMethod({ method, params = {} }) {

        if (this.methodExist(method) === false) {
            return Promise.reject({ msg: 'mopidy-out.errors.method-does-not-exist', params: { method } });
        }

        // Remove empty params
        var newParams = {};
        Object.keys(params).forEach((key) => {
            if (params[key] !== '') {
                newParams[key] = convertToInt(params[key]);
            }
        });

        return MopidyServer._executeFunctionByName(method, this._mopidy, newParams);
    }

    /**
     * Run a function from it's name in a string. String may be namedspaced,
     * e.g. 'My.Deeply.Nested.Func'
     *
     * @param functionName
     * @param context Which namespace to look for functionName in
     * @param {...} arguments Arguments which will be applied to function
     * @returns {*}
     * @private
     */
    static _executeFunctionByName(functionName, context /*, args */) {
        var args = [].slice.call(arguments).splice(2);
        var namespaces = functionName.split('.');
        var func = namespaces.pop();
        for(var i = 0; i < namespaces.length; i++) {
            context = context[namespaces[i]];
        }
        return context[func].apply(this, args);
    }

    methodExist(method) {
        return this._MOPIDY_API[method] !== undefined;
    }

    set mopidyApi(api) {
        var newApi = {};

        // Translate snake_method_names to camelCase
        Object.keys(api).forEach(function(key) {
            newApi[snakeToCamel({ name: cutCore(key) })] = api[key];
        });

        // Translate params default value to human readable
        Object.keys(newApi).forEach(function(key) {
            newApi[key].params = newApi[key].params.map((param) => {
                if (param.hasOwnProperty('default')) {
                    if (param.default === null) {
                        param.friendlyDefault = 'null';
                    } else if (param.default === true) {
                        param.friendlyDefault = 'true';
                    } else if (param.default === false) {
                        param.friendlyDefault = 'false';
                    }
                }
                return param;
            });
        });

        this._MOPIDY_API = newApi;
    }

    get name() { return this._name; }
    get host() { return this._host; }
    get port() { return this._port; }
    get readyState() {
        return objectPath.get(this, '_mopidy._webSocket.readyState', 0) === 1;
    }
    get id() { return this._id; }

    close() {
        if ('_mopidy' in this) {
            if ('close' in this._mopidy) {
                this._mopidy.close();
            }
            if ('off' in this._mopidy) {
                this._mopidy.off();
            }
        }
        this._mopidy = null;
        this._MOPIDY_API = {};
    }

    _getMethods() {
        return Object.keys(this._MOPIDY_API).map(key => {
            return {
                method: key,
                category: key.split('.')[0],
                description: this._MOPIDY_API[key].description,
                params: this._MOPIDY_API[key].params
            };
        });
    }

    getMethods() {
        return new Promise((resolve, reject) => {
            if (this.readyState) {
                resolve(this._getMethods());
            } else {
                setTimeout(() => {
                    reject({ msg: 'mopidy-out.errors.could-not-connect-to-server' });
                }, 5000);

                this.events.once('ready:ready', () => {
                    resolve(this._getMethods());
                });
            }
        });
    }

    get mopidy() { return this._mopidy }

}