From 418e5689dc59b70fdf8efa754f037919eaf326ff Mon Sep 17 00:00:00 2001 From: Paul Connolley Date: Sun, 15 Dec 2013 01:52:24 +0000 Subject: [PATCH 01/24] Preliminary commit for browserify compatibility This is the first stage of my commits for issue #609 that will make the npm module browserify friendly and browser friendly. The grunt-browserify module has been introduced to replace grunt-contrib-concat. Browserify automatically concatenates files and so there is no need for a concat step. The faye-websocket module was problematic so I moved the require out to a separate module within the lib directory. The websocket module is a folder containing a package.json, directing library consumers to the entry point that is appropriate for their environment. Browserify picks browser.js (which simply returns an object holding window.WebSocket) while everyone else gets the faye-websocket module. In addition, as browserify handles all the requires, there's no need to detect the environment or include any pre-built modules. I've removed the pre-built when and faye-websocket files in favour of letting browserify use the modules within node_modules. This should make it easier to maintain dependencies in future versions of this library. One side effect of this browserify compatibility is that, in order to allow the library to be globally available in the browser as `Mopidy`, I've had to set Mopidy as the exported object instead of as a key of the exported object. To elaborate further, the current API would be like the following: var Mopidy = require('mopidy').Mopidy; However, with this change, the API would be like this: var Mopidy = require('mopidy'); I'm not sure whether this would be an issue and so I think it's worth discussing further. It's possible that node developers won't have a problem but, if they did, a potential workaround within the mopidy.js file would be: Mopidy.Mopidy = Mopidy; This would allow developers to choose either of the following: var Mopidy = require('mopidy'); var Mopidy = require('mopidy').Mopidy; Could be a little odd to do this though When testing the browserify build, I noticed a strange error thrown when making the initial websocket connection. I managed to track it down to an IE 'feature' that crops up when you alias in-built functions. In particular, the when module was aliasing setImmediate to an internal function (nextTick.) In a newer version of when, the function is instead aliased to the browserify process.nextTick. This works well because substack already had that covered. With when@2.7.0, IE11 appears to be working well. IE10 is still pending a test. --- js/Gruntfile.js | 26 +- js/lib/bane-1.0.0.js | 171 ----- js/lib/websocket/browser.js | 1 + js/lib/websocket/package.json | 4 + js/lib/websocket/server.js | 1 + js/lib/when-2.4.0.js | 922 ----------------------- js/package.json | 4 +- js/src/mopidy.js | 20 +- mopidy/frontends/http/data/mopidy.js | 452 ++++++----- mopidy/frontends/http/data/mopidy.min.js | 4 +- 10 files changed, 294 insertions(+), 1311 deletions(-) delete mode 100644 js/lib/bane-1.0.0.js create mode 100644 js/lib/websocket/browser.js create mode 100644 js/lib/websocket/package.json create mode 100644 js/lib/websocket/server.js delete mode 100644 js/lib/when-2.4.0.js diff --git a/js/Gruntfile.js b/js/Gruntfile.js index 43a4770b..f59ed9f8 100644 --- a/js/Gruntfile.js +++ b/js/Gruntfile.js @@ -11,6 +11,7 @@ module.exports = function (grunt) { " * Licensed under the Apache License, Version 2.0 */\n", files: { own: ["Gruntfile.js", "src/**/*.js", "test/**/*-test.js"], + main: "src/mopidy.js", concat: "../mopidy/frontends/http/data/mopidy.js", minified: "../mopidy/frontends/http/data/mopidy.min.js" } @@ -18,19 +19,16 @@ module.exports = function (grunt) { buster: { all: {} }, - concat: { - options: { - banner: "<%= meta.banner %>", - stripBanners: true - }, - all: { + browserify: { + dist: { files: { - "<%= meta.files.concat %>": [ - "lib/bane-*.js", - "lib/when-define-shim.js", - "lib/when-*.js", - "src/mopidy.js" - ] + "<%= meta.files.concat %>": "<%= meta.files.main %>" + }, + options: { + postBundleCB: function (err, src, next) { + next(null, grunt.template.process("<%= meta.banner %>") + src); + }, + standalone: "Mopidy" } } }, @@ -71,11 +69,11 @@ module.exports = function (grunt) { }); grunt.registerTask("test", ["jshint", "buster"]); - grunt.registerTask("build", ["test", "concat", "uglify"]); + grunt.registerTask("build", ["test", "browserify", "uglify"]); grunt.registerTask("default", ["build"]); grunt.loadNpmTasks("grunt-buster"); - grunt.loadNpmTasks("grunt-contrib-concat"); + grunt.loadNpmTasks("grunt-browserify"); grunt.loadNpmTasks("grunt-contrib-jshint"); grunt.loadNpmTasks("grunt-contrib-uglify"); grunt.loadNpmTasks("grunt-contrib-watch"); diff --git a/js/lib/bane-1.0.0.js b/js/lib/bane-1.0.0.js deleted file mode 100644 index 8051764d..00000000 --- a/js/lib/bane-1.0.0.js +++ /dev/null @@ -1,171 +0,0 @@ -/** - * BANE - Browser globals, AMD and Node Events - * - * https://github.com/busterjs/bane - * - * @version 1.0.0 - */ - -((typeof define === "function" && define.amd && function (m) { define("bane", m); }) || - (typeof module === "object" && function (m) { module.exports = m(); }) || - function (m) { this.bane = m(); } -)(function () { - "use strict"; - var slice = Array.prototype.slice; - - function handleError(event, error, errbacks) { - var i, l = errbacks.length; - if (l > 0) { - for (i = 0; i < l; ++i) { errbacks[i](event, error); } - return; - } - setTimeout(function () { - error.message = event + " listener threw error: " + error.message; - throw error; - }, 0); - } - - function assertFunction(fn) { - if (typeof fn !== "function") { - throw new TypeError("Listener is not function"); - } - return fn; - } - - function supervisors(object) { - if (!object.supervisors) { object.supervisors = []; } - return object.supervisors; - } - - function listeners(object, event) { - if (!object.listeners) { object.listeners = {}; } - if (event && !object.listeners[event]) { object.listeners[event] = []; } - return event ? object.listeners[event] : object.listeners; - } - - function errbacks(object) { - if (!object.errbacks) { object.errbacks = []; } - return object.errbacks; - } - - /** - * @signature var emitter = bane.createEmitter([object]); - * - * Create a new event emitter. If an object is passed, it will be modified - * by adding the event emitter methods (see below). - */ - function createEventEmitter(object) { - object = object || {}; - - function notifyListener(event, listener, args) { - try { - listener.listener.apply(listener.thisp || object, args); - } catch (e) { - handleError(event, e, errbacks(object)); - } - } - - object.on = function (event, listener, thisp) { - if (typeof event === "function") { - return supervisors(this).push({ - listener: event, - thisp: listener - }); - } - listeners(this, event).push({ - listener: assertFunction(listener), - thisp: thisp - }); - }; - - object.off = function (event, listener) { - var fns, events, i, l; - if (!event) { - fns = supervisors(this); - fns.splice(0, fns.length); - - events = listeners(this); - for (i in events) { - if (events.hasOwnProperty(i)) { - fns = listeners(this, i); - fns.splice(0, fns.length); - } - } - - fns = errbacks(this); - fns.splice(0, fns.length); - - return; - } - if (typeof event === "function") { - fns = supervisors(this); - listener = event; - } else { - fns = listeners(this, event); - } - if (!listener) { - fns.splice(0, fns.length); - return; - } - for (i = 0, l = fns.length; i < l; ++i) { - if (fns[i].listener === listener) { - fns.splice(i, 1); - return; - } - } - }; - - object.once = function (event, listener, thisp) { - var wrapper = function () { - object.off(event, wrapper); - listener.apply(this, arguments); - }; - - object.on(event, wrapper, thisp); - }; - - object.bind = function (object, events) { - var prop, i, l; - if (!events) { - for (prop in object) { - if (typeof object[prop] === "function") { - this.on(prop, object[prop], object); - } - } - } else { - for (i = 0, l = events.length; i < l; ++i) { - if (typeof object[events[i]] === "function") { - this.on(events[i], object[events[i]], object); - } else { - throw new Error("No such method " + events[i]); - } - } - } - return object; - }; - - object.emit = function (event) { - var toNotify = supervisors(this); - var args = slice.call(arguments), i, l; - - for (i = 0, l = toNotify.length; i < l; ++i) { - notifyListener(event, toNotify[i], args); - } - - toNotify = listeners(this, event).slice(); - args = slice.call(arguments, 1); - for (i = 0, l = toNotify.length; i < l; ++i) { - notifyListener(event, toNotify[i], args); - } - }; - - object.errback = function (listener) { - if (!this.errbacks) { this.errbacks = []; } - this.errbacks.push(assertFunction(listener)); - }; - - return object; - } - - return { createEventEmitter: createEventEmitter }; -}); diff --git a/js/lib/websocket/browser.js b/js/lib/websocket/browser.js new file mode 100644 index 00000000..e594246c --- /dev/null +++ b/js/lib/websocket/browser.js @@ -0,0 +1 @@ +module.exports = { Client: window.WebSocket }; diff --git a/js/lib/websocket/package.json b/js/lib/websocket/package.json new file mode 100644 index 00000000..d1e2ac63 --- /dev/null +++ b/js/lib/websocket/package.json @@ -0,0 +1,4 @@ +{ + "browser": "browser.js", + "main": "server.js" +} diff --git a/js/lib/websocket/server.js b/js/lib/websocket/server.js new file mode 100644 index 00000000..dd24f4be --- /dev/null +++ b/js/lib/websocket/server.js @@ -0,0 +1 @@ +module.exports = require('faye-websocket'); diff --git a/js/lib/when-2.4.0.js b/js/lib/when-2.4.0.js deleted file mode 100644 index aa386275..00000000 --- a/js/lib/when-2.4.0.js +++ /dev/null @@ -1,922 +0,0 @@ -/** @license MIT License (c) copyright 2011-2013 original author or authors */ - -/** - * A lightweight CommonJS Promises/A and when() implementation - * when is part of the cujo.js family of libraries (http://cujojs.com/) - * - * Licensed under the MIT License at: - * http://www.opensource.org/licenses/mit-license.php - * - * @author Brian Cavalier - * @author John Hann - * @version 2.4.0 - */ -(function(define, global) { 'use strict'; -define(function (require) { - - // Public API - - when.promise = promise; // Create a pending promise - when.resolve = resolve; // Create a resolved promise - when.reject = reject; // Create a rejected promise - when.defer = defer; // Create a {promise, resolver} pair - - when.join = join; // Join 2 or more promises - - when.all = all; // Resolve a list of promises - when.map = map; // Array.map() for promises - when.reduce = reduce; // Array.reduce() for promises - when.settle = settle; // Settle a list of promises - - when.any = any; // One-winner race - when.some = some; // Multi-winner race - - when.isPromise = isPromiseLike; // DEPRECATED: use isPromiseLike - when.isPromiseLike = isPromiseLike; // Is something promise-like, aka thenable - - /** - * Register an observer for a promise or immediate value. - * - * @param {*} promiseOrValue - * @param {function?} [onFulfilled] callback to be called when promiseOrValue is - * successfully fulfilled. If promiseOrValue is an immediate value, callback - * will be invoked immediately. - * @param {function?} [onRejected] callback to be called when promiseOrValue is - * rejected. - * @param {function?} [onProgress] callback to be called when progress updates - * are issued for promiseOrValue. - * @returns {Promise} a new {@link Promise} that will complete with the return - * value of callback or errback or the completion value of promiseOrValue if - * callback and/or errback is not supplied. - */ - function when(promiseOrValue, onFulfilled, onRejected, onProgress) { - // Get a trusted promise for the input promiseOrValue, and then - // register promise handlers - return resolve(promiseOrValue).then(onFulfilled, onRejected, onProgress); - } - - /** - * Trusted Promise constructor. A Promise created from this constructor is - * a trusted when.js promise. Any other duck-typed promise is considered - * untrusted. - * @constructor - * @param {function} sendMessage function to deliver messages to the promise's handler - * @param {function?} inspect function that reports the promise's state - * @name Promise - */ - function Promise(sendMessage, inspect) { - this._message = sendMessage; - this.inspect = inspect; - } - - Promise.prototype = { - /** - * Register handlers for this promise. - * @param [onFulfilled] {Function} fulfillment handler - * @param [onRejected] {Function} rejection handler - * @param [onProgress] {Function} progress handler - * @return {Promise} new Promise - */ - then: function(onFulfilled, onRejected, onProgress) { - /*jshint unused:false*/ - var args, sendMessage; - - args = arguments; - sendMessage = this._message; - - return _promise(function(resolve, reject, notify) { - sendMessage('when', args, resolve, notify); - }, this._status && this._status.observed()); - }, - - /** - * Register a rejection handler. Shortcut for .then(undefined, onRejected) - * @param {function?} onRejected - * @return {Promise} - */ - otherwise: function(onRejected) { - return this.then(undef, onRejected); - }, - - /** - * Ensures that onFulfilledOrRejected will be called regardless of whether - * this promise is fulfilled or rejected. onFulfilledOrRejected WILL NOT - * receive the promises' value or reason. Any returned value will be disregarded. - * onFulfilledOrRejected may throw or return a rejected promise to signal - * an additional error. - * @param {function} onFulfilledOrRejected handler to be called regardless of - * fulfillment or rejection - * @returns {Promise} - */ - ensure: function(onFulfilledOrRejected) { - return this.then(injectHandler, injectHandler)['yield'](this); - - function injectHandler() { - return resolve(onFulfilledOrRejected()); - } - }, - - /** - * Shortcut for .then(function() { return value; }) - * @param {*} value - * @return {Promise} a promise that: - * - is fulfilled if value is not a promise, or - * - if value is a promise, will fulfill with its value, or reject - * with its reason. - */ - 'yield': function(value) { - return this.then(function() { - return value; - }); - }, - - /** - * Runs a side effect when this promise fulfills, without changing the - * fulfillment value. - * @param {function} onFulfilledSideEffect - * @returns {Promise} - */ - tap: function(onFulfilledSideEffect) { - return this.then(onFulfilledSideEffect)['yield'](this); - }, - - /** - * Assumes that this promise will fulfill with an array, and arranges - * for the onFulfilled to be called with the array as its argument list - * i.e. onFulfilled.apply(undefined, array). - * @param {function} onFulfilled function to receive spread arguments - * @return {Promise} - */ - spread: function(onFulfilled) { - return this.then(function(array) { - // array may contain promises, so resolve its contents. - return all(array, function(array) { - return onFulfilled.apply(undef, array); - }); - }); - }, - - /** - * Shortcut for .then(onFulfilledOrRejected, onFulfilledOrRejected) - * @deprecated - */ - always: function(onFulfilledOrRejected, onProgress) { - return this.then(onFulfilledOrRejected, onFulfilledOrRejected, onProgress); - } - }; - - /** - * Returns a resolved promise. The returned promise will be - * - fulfilled with promiseOrValue if it is a value, or - * - if promiseOrValue is a promise - * - fulfilled with promiseOrValue's value after it is fulfilled - * - rejected with promiseOrValue's reason after it is rejected - * @param {*} value - * @return {Promise} - */ - function resolve(value) { - return promise(function(resolve) { - resolve(value); - }); - } - - /** - * Returns a rejected promise for the supplied promiseOrValue. The returned - * promise will be rejected with: - * - promiseOrValue, if it is a value, or - * - if promiseOrValue is a promise - * - promiseOrValue's value after it is fulfilled - * - promiseOrValue's reason after it is rejected - * @param {*} promiseOrValue the rejected value of the returned {@link Promise} - * @return {Promise} rejected {@link Promise} - */ - function reject(promiseOrValue) { - return when(promiseOrValue, rejected); - } - - /** - * Creates a {promise, resolver} pair, either or both of which - * may be given out safely to consumers. - * The resolver has resolve, reject, and progress. The promise - * has then plus extended promise API. - * - * @return {{ - * promise: Promise, - * resolve: function:Promise, - * reject: function:Promise, - * notify: function:Promise - * resolver: { - * resolve: function:Promise, - * reject: function:Promise, - * notify: function:Promise - * }}} - */ - function defer() { - var deferred, pending, resolved; - - // Optimize object shape - deferred = { - promise: undef, resolve: undef, reject: undef, notify: undef, - resolver: { resolve: undef, reject: undef, notify: undef } - }; - - deferred.promise = pending = promise(makeDeferred); - - return deferred; - - function makeDeferred(resolvePending, rejectPending, notifyPending) { - deferred.resolve = deferred.resolver.resolve = function(value) { - if(resolved) { - return resolve(value); - } - resolved = true; - resolvePending(value); - return pending; - }; - - deferred.reject = deferred.resolver.reject = function(reason) { - if(resolved) { - return resolve(rejected(reason)); - } - resolved = true; - rejectPending(reason); - return pending; - }; - - deferred.notify = deferred.resolver.notify = function(update) { - notifyPending(update); - return update; - }; - } - } - - /** - * Creates a new promise whose fate is determined by resolver. - * @param {function} resolver function(resolve, reject, notify) - * @returns {Promise} promise whose fate is determine by resolver - */ - function promise(resolver) { - return _promise(resolver, monitorApi.PromiseStatus && monitorApi.PromiseStatus()); - } - - /** - * Creates a new promise, linked to parent, whose fate is determined - * by resolver. - * @param {function} resolver function(resolve, reject, notify) - * @param {Promise?} status promise from which the new promise is begotten - * @returns {Promise} promise whose fate is determine by resolver - * @private - */ - function _promise(resolver, status) { - var self, value, consumers = []; - - self = new Promise(_message, inspect); - self._status = status; - - // Call the provider resolver to seal the promise's fate - try { - resolver(promiseResolve, promiseReject, promiseNotify); - } catch(e) { - promiseReject(e); - } - - // Return the promise - return self; - - /** - * Private message delivery. Queues and delivers messages to - * the promise's ultimate fulfillment value or rejection reason. - * @private - * @param {String} type - * @param {Array} args - * @param {Function} resolve - * @param {Function} notify - */ - function _message(type, args, resolve, notify) { - consumers ? consumers.push(deliver) : enqueue(function() { deliver(value); }); - - function deliver(p) { - p._message(type, args, resolve, notify); - } - } - - /** - * Returns a snapshot of the promise's state at the instant inspect() - * is called. The returned object is not live and will not update as - * the promise's state changes. - * @returns {{ state:String, value?:*, reason?:* }} status snapshot - * of the promise. - */ - function inspect() { - return value ? value.inspect() : toPendingState(); - } - - /** - * Transition from pre-resolution state to post-resolution state, notifying - * all listeners of the ultimate fulfillment or rejection - * @param {*|Promise} val resolution value - */ - function promiseResolve(val) { - if(!consumers) { - return; - } - - value = coerce(val); - scheduleConsumers(consumers, value); - consumers = undef; - - if(status) { - updateStatus(value, status); - } - } - - /** - * Reject this promise with the supplied reason, which will be used verbatim. - * @param {*} reason reason for the rejection - */ - function promiseReject(reason) { - promiseResolve(rejected(reason)); - } - - /** - * Issue a progress event, notifying all progress listeners - * @param {*} update progress event payload to pass to all listeners - */ - function promiseNotify(update) { - if(consumers) { - scheduleConsumers(consumers, progressed(update)); - } - } - } - - /** - * Creates a fulfilled, local promise as a proxy for a value - * NOTE: must never be exposed - * @param {*} value fulfillment value - * @returns {Promise} - */ - function fulfilled(value) { - return near( - new NearFulfilledProxy(value), - function() { return toFulfilledState(value); } - ); - } - - /** - * Creates a rejected, local promise with the supplied reason - * NOTE: must never be exposed - * @param {*} reason rejection reason - * @returns {Promise} - */ - function rejected(reason) { - return near( - new NearRejectedProxy(reason), - function() { return toRejectedState(reason); } - ); - } - - /** - * Creates a near promise using the provided proxy - * NOTE: must never be exposed - * @param {object} proxy proxy for the promise's ultimate value or reason - * @param {function} inspect function that returns a snapshot of the - * returned near promise's state - * @returns {Promise} - */ - function near(proxy, inspect) { - return new Promise(function (type, args, resolve) { - try { - resolve(proxy[type].apply(proxy, args)); - } catch(e) { - resolve(rejected(e)); - } - }, inspect); - } - - /** - * Create a progress promise with the supplied update. - * @private - * @param {*} update - * @return {Promise} progress promise - */ - function progressed(update) { - return new Promise(function (type, args, _, notify) { - var onProgress = args[2]; - try { - notify(typeof onProgress === 'function' ? onProgress(update) : update); - } catch(e) { - notify(e); - } - }); - } - - /** - * Coerces x to a trusted Promise - * - * @private - * @param {*} x thing to coerce - * @returns {*} Guaranteed to return a trusted Promise. If x - * is trusted, returns x, otherwise, returns a new, trusted, already-resolved - * Promise whose resolution value is: - * * the resolution value of x if it's a foreign promise, or - * * x if it's a value - */ - function coerce(x) { - if (x instanceof Promise) { - return x; - } - - if (!(x === Object(x) && 'then' in x)) { - return fulfilled(x); - } - - return promise(function(resolve, reject, notify) { - enqueue(function() { - try { - // We must check and assimilate in the same tick, but not the - // current tick, careful only to access promiseOrValue.then once. - var untrustedThen = x.then; - - if(typeof untrustedThen === 'function') { - fcall(untrustedThen, x, resolve, reject, notify); - } else { - // It's a value, create a fulfilled wrapper - resolve(fulfilled(x)); - } - - } catch(e) { - // Something went wrong, reject - reject(e); - } - }); - }); - } - - /** - * Proxy for a near, fulfilled value - * @param {*} value - * @constructor - */ - function NearFulfilledProxy(value) { - this.value = value; - } - - NearFulfilledProxy.prototype.when = function(onResult) { - return typeof onResult === 'function' ? onResult(this.value) : this.value; - }; - - /** - * Proxy for a near rejection - * @param {*} reason - * @constructor - */ - function NearRejectedProxy(reason) { - this.reason = reason; - } - - NearRejectedProxy.prototype.when = function(_, onError) { - if(typeof onError === 'function') { - return onError(this.reason); - } else { - throw this.reason; - } - }; - - /** - * Schedule a task that will process a list of handlers - * in the next queue drain run. - * @private - * @param {Array} handlers queue of handlers to execute - * @param {*} value passed as the only arg to each handler - */ - function scheduleConsumers(handlers, value) { - enqueue(function() { - var handler, i = 0; - while (handler = handlers[i++]) { - handler(value); - } - }); - } - - function updateStatus(value, status) { - value.then(statusFulfilled, statusRejected); - - function statusFulfilled() { status.fulfilled(); } - function statusRejected(r) { status.rejected(r); } - } - - /** - * Determines if x is promise-like, i.e. a thenable object - * NOTE: Will return true for *any thenable object*, and isn't truly - * safe, since it may attempt to access the `then` property of x (i.e. - * clever/malicious getters may do weird things) - * @param {*} x anything - * @returns {boolean} true if x is promise-like - */ - function isPromiseLike(x) { - return x && typeof x.then === 'function'; - } - - /** - * Initiates a competitive race, returning a promise that will resolve when - * howMany of the supplied promisesOrValues have resolved, or will reject when - * it becomes impossible for howMany to resolve, for example, when - * (promisesOrValues.length - howMany) + 1 input promises reject. - * - * @param {Array} promisesOrValues array of anything, may contain a mix - * of promises and values - * @param howMany {number} number of promisesOrValues to resolve - * @param {function?} [onFulfilled] DEPRECATED, use returnedPromise.then() - * @param {function?} [onRejected] DEPRECATED, use returnedPromise.then() - * @param {function?} [onProgress] DEPRECATED, use returnedPromise.then() - * @returns {Promise} promise that will resolve to an array of howMany values that - * resolved first, or will reject with an array of - * (promisesOrValues.length - howMany) + 1 rejection reasons. - */ - function some(promisesOrValues, howMany, onFulfilled, onRejected, onProgress) { - - return when(promisesOrValues, function(promisesOrValues) { - - return promise(resolveSome).then(onFulfilled, onRejected, onProgress); - - function resolveSome(resolve, reject, notify) { - var toResolve, toReject, values, reasons, fulfillOne, rejectOne, len, i; - - len = promisesOrValues.length >>> 0; - - toResolve = Math.max(0, Math.min(howMany, len)); - values = []; - - toReject = (len - toResolve) + 1; - reasons = []; - - // No items in the input, resolve immediately - if (!toResolve) { - resolve(values); - - } else { - rejectOne = function(reason) { - reasons.push(reason); - if(!--toReject) { - fulfillOne = rejectOne = identity; - reject(reasons); - } - }; - - fulfillOne = function(val) { - // This orders the values based on promise resolution order - values.push(val); - if (!--toResolve) { - fulfillOne = rejectOne = identity; - resolve(values); - } - }; - - for(i = 0; i < len; ++i) { - if(i in promisesOrValues) { - when(promisesOrValues[i], fulfiller, rejecter, notify); - } - } - } - - function rejecter(reason) { - rejectOne(reason); - } - - function fulfiller(val) { - fulfillOne(val); - } - } - }); - } - - /** - * Initiates a competitive race, returning a promise that will resolve when - * any one of the supplied promisesOrValues has resolved or will reject when - * *all* promisesOrValues have rejected. - * - * @param {Array|Promise} promisesOrValues array of anything, may contain a mix - * of {@link Promise}s and values - * @param {function?} [onFulfilled] DEPRECATED, use returnedPromise.then() - * @param {function?} [onRejected] DEPRECATED, use returnedPromise.then() - * @param {function?} [onProgress] DEPRECATED, use returnedPromise.then() - * @returns {Promise} promise that will resolve to the value that resolved first, or - * will reject with an array of all rejected inputs. - */ - function any(promisesOrValues, onFulfilled, onRejected, onProgress) { - - function unwrapSingleResult(val) { - return onFulfilled ? onFulfilled(val[0]) : val[0]; - } - - return some(promisesOrValues, 1, unwrapSingleResult, onRejected, onProgress); - } - - /** - * Return a promise that will resolve only once all the supplied promisesOrValues - * have resolved. The resolution value of the returned promise will be an array - * containing the resolution values of each of the promisesOrValues. - * @memberOf when - * - * @param {Array|Promise} promisesOrValues array of anything, may contain a mix - * of {@link Promise}s and values - * @param {function?} [onFulfilled] DEPRECATED, use returnedPromise.then() - * @param {function?} [onRejected] DEPRECATED, use returnedPromise.then() - * @param {function?} [onProgress] DEPRECATED, use returnedPromise.then() - * @returns {Promise} - */ - function all(promisesOrValues, onFulfilled, onRejected, onProgress) { - return _map(promisesOrValues, identity).then(onFulfilled, onRejected, onProgress); - } - - /** - * Joins multiple promises into a single returned promise. - * @return {Promise} a promise that will fulfill when *all* the input promises - * have fulfilled, or will reject when *any one* of the input promises rejects. - */ - function join(/* ...promises */) { - return _map(arguments, identity); - } - - /** - * Settles all input promises such that they are guaranteed not to - * be pending once the returned promise fulfills. The returned promise - * will always fulfill, except in the case where `array` is a promise - * that rejects. - * @param {Array|Promise} array or promise for array of promises to settle - * @returns {Promise} promise that always fulfills with an array of - * outcome snapshots for each input promise. - */ - function settle(array) { - return _map(array, toFulfilledState, toRejectedState); - } - - /** - * Promise-aware array map function, similar to `Array.prototype.map()`, - * but input array may contain promises or values. - * @param {Array|Promise} array array of anything, may contain promises and values - * @param {function} mapFunc map function which may return a promise or value - * @returns {Promise} promise that will fulfill with an array of mapped values - * or reject if any input promise rejects. - */ - function map(array, mapFunc) { - return _map(array, mapFunc); - } - - /** - * Internal map that allows a fallback to handle rejections - * @param {Array|Promise} array array of anything, may contain promises and values - * @param {function} mapFunc map function which may return a promise or value - * @param {function?} fallback function to handle rejected promises - * @returns {Promise} promise that will fulfill with an array of mapped values - * or reject if any input promise rejects. - */ - function _map(array, mapFunc, fallback) { - return when(array, function(array) { - - return _promise(resolveMap); - - function resolveMap(resolve, reject, notify) { - var results, len, toResolve, i; - - // Since we know the resulting length, we can preallocate the results - // array to avoid array expansions. - toResolve = len = array.length >>> 0; - results = []; - - if(!toResolve) { - resolve(results); - return; - } - - // Since mapFunc may be async, get all invocations of it into flight - for(i = 0; i < len; i++) { - if(i in array) { - resolveOne(array[i], i); - } else { - --toResolve; - } - } - - function resolveOne(item, i) { - when(item, mapFunc, fallback).then(function(mapped) { - results[i] = mapped; - notify(mapped); - - if(!--toResolve) { - resolve(results); - } - }, reject); - } - } - }); - } - - /** - * Traditional reduce function, similar to `Array.prototype.reduce()`, but - * input may contain promises and/or values, and reduceFunc - * may return either a value or a promise, *and* initialValue may - * be a promise for the starting value. - * - * @param {Array|Promise} promise array or promise for an array of anything, - * may contain a mix of promises and values. - * @param {function} reduceFunc reduce function reduce(currentValue, nextValue, index, total), - * where total is the total number of items being reduced, and will be the same - * in each call to reduceFunc. - * @returns {Promise} that will resolve to the final reduced value - */ - function reduce(promise, reduceFunc /*, initialValue */) { - var args = fcall(slice, arguments, 1); - - return when(promise, function(array) { - var total; - - total = array.length; - - // Wrap the supplied reduceFunc with one that handles promises and then - // delegates to the supplied. - args[0] = function (current, val, i) { - return when(current, function (c) { - return when(val, function (value) { - return reduceFunc(c, value, i, total); - }); - }); - }; - - return reduceArray.apply(array, args); - }); - } - - // Snapshot states - - /** - * Creates a fulfilled state snapshot - * @private - * @param {*} x any value - * @returns {{state:'fulfilled',value:*}} - */ - function toFulfilledState(x) { - return { state: 'fulfilled', value: x }; - } - - /** - * Creates a rejected state snapshot - * @private - * @param {*} x any reason - * @returns {{state:'rejected',reason:*}} - */ - function toRejectedState(x) { - return { state: 'rejected', reason: x }; - } - - /** - * Creates a pending state snapshot - * @private - * @returns {{state:'pending'}} - */ - function toPendingState() { - return { state: 'pending' }; - } - - // - // Internals, utilities, etc. - // - - var reduceArray, slice, fcall, nextTick, handlerQueue, - setTimeout, funcProto, call, arrayProto, monitorApi, - cjsRequire, undef; - - cjsRequire = require; - - // - // Shared handler queue processing - // - // Credit to Twisol (https://github.com/Twisol) for suggesting - // this type of extensible queue + trampoline approach for - // next-tick conflation. - - handlerQueue = []; - - /** - * Enqueue a task. If the queue is not currently scheduled to be - * drained, schedule it. - * @param {function} task - */ - function enqueue(task) { - if(handlerQueue.push(task) === 1) { - nextTick(drainQueue); - } - } - - /** - * Drain the handler queue entirely, being careful to allow the - * queue to be extended while it is being processed, and to continue - * processing until it is truly empty. - */ - function drainQueue() { - var task, i = 0; - - while(task = handlerQueue[i++]) { - task(); - } - - handlerQueue = []; - } - - // capture setTimeout to avoid being caught by fake timers - // used in time based tests - setTimeout = global.setTimeout; - - // Allow attaching the monitor to when() if env has no console - monitorApi = typeof console != 'undefined' ? console : when; - - // Prefer setImmediate or MessageChannel, cascade to node, - // vertx and finally setTimeout - /*global setImmediate,MessageChannel,process*/ - if (typeof setImmediate === 'function') { - nextTick = setImmediate.bind(global); - } else if(typeof MessageChannel !== 'undefined') { - var channel = new MessageChannel(); - channel.port1.onmessage = drainQueue; - nextTick = function() { channel.port2.postMessage(0); }; - } else if (typeof process === 'object' && process.nextTick) { - nextTick = process.nextTick; - } else { - try { - // vert.x 1.x || 2.x - nextTick = cjsRequire('vertx').runOnLoop || cjsRequire('vertx').runOnContext; - } catch(ignore) { - nextTick = function(t) { setTimeout(t, 0); }; - } - } - - // - // Capture/polyfill function and array utils - // - - // Safe function calls - funcProto = Function.prototype; - call = funcProto.call; - fcall = funcProto.bind - ? call.bind(call) - : function(f, context) { - return f.apply(context, slice.call(arguments, 2)); - }; - - // Safe array ops - arrayProto = []; - slice = arrayProto.slice; - - // ES5 reduce implementation if native not available - // See: http://es5.github.com/#x15.4.4.21 as there are many - // specifics and edge cases. ES5 dictates that reduce.length === 1 - // This implementation deviates from ES5 spec in the following ways: - // 1. It does not check if reduceFunc is a Callable - reduceArray = arrayProto.reduce || - function(reduceFunc /*, initialValue */) { - /*jshint maxcomplexity: 7*/ - var arr, args, reduced, len, i; - - i = 0; - arr = Object(this); - len = arr.length >>> 0; - args = arguments; - - // If no initialValue, use first item of array (we know length !== 0 here) - // and adjust i to start at second item - if(args.length <= 1) { - // Skip to the first real element in the array - for(;;) { - if(i in arr) { - reduced = arr[i++]; - break; - } - - // If we reached the end of the array without finding any real - // elements, it's a TypeError - if(++i >= len) { - throw new TypeError(); - } - } - } else { - // If initialValue provided, use it - reduced = args[1]; - } - - // Do the actual reduce - for(;i < len; ++i) { - if(i in arr) { - reduced = reduceFunc(reduced, arr[i], i, arr); - } - } - - return reduced; - }; - - function identity(x) { - return x; - } - - return when; -}); -})(typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); }, this); diff --git a/js/package.json b/js/package.json index 5b8e46d8..6d6c8a89 100644 --- a/js/package.json +++ b/js/package.json @@ -16,13 +16,13 @@ "dependencies": { "bane": "~1.0.0", "faye-websocket": "~0.7.0", - "when": "~2.4.0" + "when": "~2.7.0" }, "devDependencies": { "buster": "~0.6.13", "grunt": "~0.4.1", "grunt-buster": "~0.2.1", - "grunt-contrib-concat": "~0.3.0", + "grunt-browserify": "~1.3.0", "grunt-contrib-jshint": "~0.6.4", "grunt-contrib-uglify": "~0.2.4", "grunt-contrib-watch": "~0.5.3", diff --git a/js/src/mopidy.js b/js/src/mopidy.js index 980256b5..1667f9b1 100644 --- a/js/src/mopidy.js +++ b/js/src/mopidy.js @@ -1,10 +1,8 @@ -/*global exports:false, require:false*/ +/*global module:true, require:false*/ -if (typeof module === "object" && typeof require === "function") { - var bane = require("bane"); - var websocket = require("faye-websocket"); - var when = require("when"); -} +var bane = require("bane"); +var websocket = require("../lib/websocket/"); +var when = require("when"); function Mopidy(settings) { if (!(this instanceof Mopidy)) { @@ -26,11 +24,7 @@ function Mopidy(settings) { } } -if (typeof module === "object" && typeof require === "function") { - Mopidy.WebSocket = websocket.Client; -} else { - Mopidy.WebSocket = window.WebSocket; -} +Mopidy.WebSocket = websocket.Client; Mopidy.prototype._configure = function (settings) { var currentHost = (typeof document !== "undefined" && @@ -295,6 +289,4 @@ Mopidy.prototype._snakeToCamel = function (name) { }); }; -if (typeof exports === "object") { - exports.Mopidy = Mopidy; -} +module.exports = Mopidy; diff --git a/mopidy/frontends/http/data/mopidy.js b/mopidy/frontends/http/data/mopidy.js index 3e4e832e..857d826b 100644 --- a/mopidy/frontends/http/data/mopidy.js +++ b/mopidy/frontends/http/data/mopidy.js @@ -1,7 +1,11 @@ -/*! Mopidy.js - built 2013-09-17 +/*! Mopidy.js - built 2013-12-15 * http://www.mopidy.com/ * Copyright (c) 2013 Stein Magnus Jodal and contributors * Licensed under the Apache License, Version 2.0 */ +!function(e){"object"==typeof exports?module.exports=e():"function"==typeof define&&define.amd?define(e):"undefined"!=typeof window?window.Mopidy=e():"undefined"!=typeof global?global.Mopidy=e():"undefined"!=typeof self&&(self.Mopidy=e())}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 0) { + var fn = queue.shift(); + fn(); + } + } + }, true); + + return function nextTick(fn) { + queue.push(fn); + window.postMessage('process-tick', '*'); + }; + } + + return function nextTick(fn) { + setTimeout(fn, 0); }; - window.define.amd = {}; +})(); + +process.title = 'browser'; +process.browser = true; +process.env = {}; +process.argv = []; + +process.binding = function (name) { + throw new Error('process.binding is not supported'); } +// TODO(shtylman) +process.cwd = function () { return '/' }; +process.chdir = function (dir) { + throw new Error('process.chdir is not supported'); +}; + +},{}],4:[function(require,module,exports){ +var process=require("__browserify_process");/** @license MIT License (c) copyright 2011-2013 original author or authors */ + /** * A lightweight CommonJS Promises/A and when() implementation * when is part of the cujo.js family of libraries (http://cujojs.com/) @@ -187,9 +236,9 @@ if (typeof window !== "undefined") { * * @author Brian Cavalier * @author John Hann - * @version 2.4.0 + * @version 2.7.0 */ -(function(define, global) { 'use strict'; +(function(define) { 'use strict'; define(function (require) { // Public API @@ -230,7 +279,11 @@ define(function (require) { function when(promiseOrValue, onFulfilled, onRejected, onProgress) { // Get a trusted promise for the input promiseOrValue, and then // register promise handlers - return resolve(promiseOrValue).then(onFulfilled, onRejected, onProgress); + return cast(promiseOrValue).then(onFulfilled, onRejected, onProgress); + } + + function cast(x) { + return x instanceof Promise ? x : resolve(x); } /** @@ -247,102 +300,118 @@ define(function (require) { this.inspect = inspect; } - Promise.prototype = { - /** - * Register handlers for this promise. - * @param [onFulfilled] {Function} fulfillment handler - * @param [onRejected] {Function} rejection handler - * @param [onProgress] {Function} progress handler - * @return {Promise} new Promise - */ - then: function(onFulfilled, onRejected, onProgress) { - /*jshint unused:false*/ - var args, sendMessage; + var promisePrototype = Promise.prototype; - args = arguments; - sendMessage = this._message; + /** + * Register handlers for this promise. + * @param [onFulfilled] {Function} fulfillment handler + * @param [onRejected] {Function} rejection handler + * @param [onProgress] {Function} progress handler + * @return {Promise} new Promise + */ + promisePrototype.then = function(onFulfilled, onRejected, onProgress) { + /*jshint unused:false*/ + var args, sendMessage; - return _promise(function(resolve, reject, notify) { - sendMessage('when', args, resolve, notify); - }, this._status && this._status.observed()); - }, + args = arguments; + sendMessage = this._message; - /** - * Register a rejection handler. Shortcut for .then(undefined, onRejected) - * @param {function?} onRejected - * @return {Promise} - */ - otherwise: function(onRejected) { - return this.then(undef, onRejected); - }, + return _promise(function(resolve, reject, notify) { + sendMessage('when', args, resolve, notify); + }, this._status && this._status.observed()); + }; - /** - * Ensures that onFulfilledOrRejected will be called regardless of whether - * this promise is fulfilled or rejected. onFulfilledOrRejected WILL NOT - * receive the promises' value or reason. Any returned value will be disregarded. - * onFulfilledOrRejected may throw or return a rejected promise to signal - * an additional error. - * @param {function} onFulfilledOrRejected handler to be called regardless of - * fulfillment or rejection - * @returns {Promise} - */ - ensure: function(onFulfilledOrRejected) { - return this.then(injectHandler, injectHandler)['yield'](this); + /** + * Register a rejection handler. Shortcut for .then(undefined, onRejected) + * @param {function?} onRejected + * @return {Promise} + */ + promisePrototype['catch'] = promisePrototype.otherwise = function(onRejected) { + return this.then(undef, onRejected); + }; - function injectHandler() { - return resolve(onFulfilledOrRejected()); - } - }, + /** + * Ensures that onFulfilledOrRejected will be called regardless of whether + * this promise is fulfilled or rejected. onFulfilledOrRejected WILL NOT + * receive the promises' value or reason. Any returned value will be disregarded. + * onFulfilledOrRejected may throw or return a rejected promise to signal + * an additional error. + * @param {function} onFulfilledOrRejected handler to be called regardless of + * fulfillment or rejection + * @returns {Promise} + */ + promisePrototype['finally'] = promisePrototype.ensure = function(onFulfilledOrRejected) { + return typeof onFulfilledOrRejected === 'function' + ? this.then(injectHandler, injectHandler)['yield'](this) + : this; - /** - * Shortcut for .then(function() { return value; }) - * @param {*} value - * @return {Promise} a promise that: - * - is fulfilled if value is not a promise, or - * - if value is a promise, will fulfill with its value, or reject - * with its reason. - */ - 'yield': function(value) { - return this.then(function() { - return value; - }); - }, - - /** - * Runs a side effect when this promise fulfills, without changing the - * fulfillment value. - * @param {function} onFulfilledSideEffect - * @returns {Promise} - */ - tap: function(onFulfilledSideEffect) { - return this.then(onFulfilledSideEffect)['yield'](this); - }, - - /** - * Assumes that this promise will fulfill with an array, and arranges - * for the onFulfilled to be called with the array as its argument list - * i.e. onFulfilled.apply(undefined, array). - * @param {function} onFulfilled function to receive spread arguments - * @return {Promise} - */ - spread: function(onFulfilled) { - return this.then(function(array) { - // array may contain promises, so resolve its contents. - return all(array, function(array) { - return onFulfilled.apply(undef, array); - }); - }); - }, - - /** - * Shortcut for .then(onFulfilledOrRejected, onFulfilledOrRejected) - * @deprecated - */ - always: function(onFulfilledOrRejected, onProgress) { - return this.then(onFulfilledOrRejected, onFulfilledOrRejected, onProgress); + function injectHandler() { + return resolve(onFulfilledOrRejected()); } }; + /** + * Terminate a promise chain by handling the ultimate fulfillment value or + * rejection reason, and assuming responsibility for all errors. if an + * error propagates out of handleResult or handleFatalError, it will be + * rethrown to the host, resulting in a loud stack track on most platforms + * and a crash on some. + * @param {function?} handleResult + * @param {function?} handleError + * @returns {undefined} + */ + promisePrototype.done = function(handleResult, handleError) { + this.then(handleResult, handleError).otherwise(crash); + }; + + /** + * Shortcut for .then(function() { return value; }) + * @param {*} value + * @return {Promise} a promise that: + * - is fulfilled if value is not a promise, or + * - if value is a promise, will fulfill with its value, or reject + * with its reason. + */ + promisePrototype['yield'] = function(value) { + return this.then(function() { + return value; + }); + }; + + /** + * Runs a side effect when this promise fulfills, without changing the + * fulfillment value. + * @param {function} onFulfilledSideEffect + * @returns {Promise} + */ + promisePrototype.tap = function(onFulfilledSideEffect) { + return this.then(onFulfilledSideEffect)['yield'](this); + }; + + /** + * Assumes that this promise will fulfill with an array, and arranges + * for the onFulfilled to be called with the array as its argument list + * i.e. onFulfilled.apply(undefined, array). + * @param {function} onFulfilled function to receive spread arguments + * @return {Promise} + */ + promisePrototype.spread = function(onFulfilled) { + return this.then(function(array) { + // array may contain promises, so resolve its contents. + return all(array, function(array) { + return onFulfilled.apply(undef, array); + }); + }); + }; + + /** + * Shortcut for .then(onFulfilledOrRejected, onFulfilledOrRejected) + * @deprecated + */ + promisePrototype.always = function(onFulfilledOrRejected, onProgress) { + return this.then(onFulfilledOrRejected, onFulfilledOrRejected, onProgress); + }; + /** * Returns a resolved promise. The returned promise will be * - fulfilled with promiseOrValue if it is a value, or @@ -499,13 +568,17 @@ define(function (require) { return; } - value = coerce(val); - scheduleConsumers(consumers, value); + var queue = consumers; consumers = undef; - if(status) { - updateStatus(value, status); - } + enqueue(function () { + value = coerce(self, val); + if(status) { + updateStatus(value, status); + } + runHandlers(queue, value); + }); + } /** @@ -522,11 +595,24 @@ define(function (require) { */ function promiseNotify(update) { if(consumers) { - scheduleConsumers(consumers, progressed(update)); + var queue = consumers; + enqueue(function () { + runHandlers(queue, progressed(update)); + }); } } } + /** + * Run a queue of functions as quickly as possible, passing + * value to each. + */ + function runHandlers(queue, value) { + for (var i = 0; i < queue.length; i++) { + queue[i](value); + } + } + /** * Creates a fulfilled, local promise as a proxy for a value * NOTE: must never be exposed @@ -590,8 +676,6 @@ define(function (require) { /** * Coerces x to a trusted Promise - * - * @private * @param {*} x thing to coerce * @returns {*} Guaranteed to return a trusted Promise. If x * is trusted, returns x, otherwise, returns a new, trusted, already-resolved @@ -599,34 +683,35 @@ define(function (require) { * * the resolution value of x if it's a foreign promise, or * * x if it's a value */ - function coerce(x) { + function coerce(self, x) { + if (x === self) { + return rejected(new TypeError()); + } + if (x instanceof Promise) { return x; } - if (!(x === Object(x) && 'then' in x)) { - return fulfilled(x); + try { + var untrustedThen = x === Object(x) && x.then; + + return typeof untrustedThen === 'function' + ? assimilate(untrustedThen, x) + : fulfilled(x); + } catch(e) { + return rejected(e); } + } - return promise(function(resolve, reject, notify) { - enqueue(function() { - try { - // We must check and assimilate in the same tick, but not the - // current tick, careful only to access promiseOrValue.then once. - var untrustedThen = x.then; - - if(typeof untrustedThen === 'function') { - fcall(untrustedThen, x, resolve, reject, notify); - } else { - // It's a value, create a fulfilled wrapper - resolve(fulfilled(x)); - } - - } catch(e) { - // Something went wrong, reject - reject(e); - } - }); + /** + * Safely assimilates a foreign thenable by wrapping it in a trusted promise + * @param {function} untrustedThen x's then() method + * @param {object|function} x thenable + * @returns {Promise} + */ + function assimilate(untrustedThen, x) { + return promise(function (resolve, reject) { + fcall(untrustedThen, x, resolve, reject); }); } @@ -660,22 +745,6 @@ define(function (require) { } }; - /** - * Schedule a task that will process a list of handlers - * in the next queue drain run. - * @private - * @param {Array} handlers queue of handlers to execute - * @param {*} value passed as the only arg to each handler - */ - function scheduleConsumers(handlers, value) { - enqueue(function() { - var handler, i = 0; - while (handler = handlers[i++]) { - handler(value); - } - }); - } - function updateStatus(value, status) { value.then(statusFulfilled, statusRejected); @@ -879,12 +948,11 @@ define(function (require) { function resolveOne(item, i) { when(item, mapFunc, fallback).then(function(mapped) { results[i] = mapped; - notify(mapped); if(!--toResolve) { resolve(results); } - }, reject); + }, reject, notify); } } }); @@ -961,8 +1029,8 @@ define(function (require) { // var reduceArray, slice, fcall, nextTick, handlerQueue, - setTimeout, funcProto, call, arrayProto, monitorApi, - cjsRequire, undef; + funcProto, call, arrayProto, monitorApi, + capturedSetTimeout, cjsRequire, MutationObs, undef; cjsRequire = require; @@ -992,39 +1060,39 @@ define(function (require) { * processing until it is truly empty. */ function drainQueue() { - var task, i = 0; - - while(task = handlerQueue[i++]) { - task(); - } - + runHandlers(handlerQueue); handlerQueue = []; } - // capture setTimeout to avoid being caught by fake timers - // used in time based tests - setTimeout = global.setTimeout; - // Allow attaching the monitor to when() if env has no console - monitorApi = typeof console != 'undefined' ? console : when; + monitorApi = typeof console !== 'undefined' ? console : when; - // Prefer setImmediate or MessageChannel, cascade to node, - // vertx and finally setTimeout - /*global setImmediate,MessageChannel,process*/ - if (typeof setImmediate === 'function') { - nextTick = setImmediate.bind(global); - } else if(typeof MessageChannel !== 'undefined') { - var channel = new MessageChannel(); - channel.port1.onmessage = drainQueue; - nextTick = function() { channel.port2.postMessage(0); }; - } else if (typeof process === 'object' && process.nextTick) { + // Sniff "best" async scheduling option + // Prefer process.nextTick or MutationObserver, then check for + // vertx and finally fall back to setTimeout + /*global process,document,setTimeout,MutationObserver,WebKitMutationObserver*/ + if (typeof process === 'object' && process.nextTick) { nextTick = process.nextTick; + } else if(MutationObs = + (typeof MutationObserver === 'function' && MutationObserver) || + (typeof WebKitMutationObserver === 'function' && WebKitMutationObserver)) { + nextTick = (function(document, MutationObserver, drainQueue) { + var el = document.createElement('div'); + new MutationObserver(drainQueue).observe(el, { attributes: true }); + + return function() { + el.setAttribute('x', 'x'); + }; + }(document, MutationObs, drainQueue)); } else { try { // vert.x 1.x || 2.x nextTick = cjsRequire('vertx').runOnLoop || cjsRequire('vertx').runOnContext; } catch(ignore) { - nextTick = function(t) { setTimeout(t, 0); }; + // capture setTimeout to avoid being caught by fake timers + // used in time based tests + capturedSetTimeout = setTimeout; + nextTick = function(t) { capturedSetTimeout(t, 0); }; } } @@ -1095,15 +1163,28 @@ define(function (require) { return x; } + function crash(fatalError) { + if(typeof monitorApi.reportUnhandled === 'function') { + monitorApi.reportUnhandled(); + } else { + enqueue(function() { + throw fatalError; + }); + } + + throw fatalError; + } + return when; }); -})(typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); }, this); +})(typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); }); -if (typeof module === "object" && typeof require === "function") { - var bane = require("bane"); - var websocket = require("faye-websocket"); - var when = require("when"); -} +},{"__browserify_process":3}],5:[function(require,module,exports){ +/*global module:true, require:false*/ + +var bane = require("bane"); +var websocket = require("../lib/websocket/"); +var when = require("when"); function Mopidy(settings) { if (!(this instanceof Mopidy)) { @@ -1125,11 +1206,7 @@ function Mopidy(settings) { } } -if (typeof module === "object" && typeof require === "function") { - Mopidy.WebSocket = websocket.Client; -} else { - Mopidy.WebSocket = window.WebSocket; -} +Mopidy.WebSocket = websocket.Client; Mopidy.prototype._configure = function (settings) { var currentHost = (typeof document !== "undefined" && @@ -1394,6 +1471,9 @@ Mopidy.prototype._snakeToCamel = function (name) { }); }; -if (typeof exports === "object") { - exports.Mopidy = Mopidy; -} +module.exports = Mopidy; + +},{"../lib/websocket/":1,"bane":2,"when":4}]},{},[5]) +(5) +}); +; \ No newline at end of file diff --git a/mopidy/frontends/http/data/mopidy.min.js b/mopidy/frontends/http/data/mopidy.min.js index 75d9fff1..5e61a3f6 100644 --- a/mopidy/frontends/http/data/mopidy.min.js +++ b/mopidy/frontends/http/data/mopidy.min.js @@ -1,5 +1,5 @@ -/*! Mopidy.js - built 2013-09-17 +/*! Mopidy.js - built 2013-12-15 * http://www.mopidy.com/ * Copyright (c) 2013 Stein Magnus Jodal and contributors * Licensed under the Apache License, Version 2.0 */ -function Mopidy(a){return this instanceof Mopidy?(this._settings=this._configure(a||{}),this._console=this._getConsole(),this._backoffDelay=this._settings.backoffDelayMin,this._pendingRequests={},this._webSocket=null,bane.createEventEmitter(this),this._delegateEvents(),this._settings.autoConnect&&this.connect(),void 0):new Mopidy(a)}if(("function"==typeof define&&define.amd&&function(a){define("bane",a)}||"object"==typeof module&&function(a){module.exports=a()}||function(a){this.bane=a()})(function(){"use strict";function a(a,b,c){var d,e=c.length;if(e>0)for(d=0;e>d;++d)c[d](a,b);else setTimeout(function(){throw b.message=a+" listener threw error: "+b.message,b},0)}function b(a){if("function"!=typeof a)throw new TypeError("Listener is not function");return a}function c(a){return a.supervisors||(a.supervisors=[]),a.supervisors}function d(a,b){return a.listeners||(a.listeners={}),b&&!a.listeners[b]&&(a.listeners[b]=[]),b?a.listeners[b]:a.listeners}function e(a){return a.errbacks||(a.errbacks=[]),a.errbacks}function f(f){function h(b,c,d){try{c.listener.apply(c.thisp||f,d)}catch(g){a(b,g,e(f))}}return f=f||{},f.on=function(a,e,f){return"function"==typeof a?c(this).push({listener:a,thisp:e}):(d(this,a).push({listener:b(e),thisp:f}),void 0)},f.off=function(a,b){var f,g,h,i;if(!a){f=c(this),f.splice(0,f.length),g=d(this);for(h in g)g.hasOwnProperty(h)&&(f=d(this,h),f.splice(0,f.length));return f=e(this),f.splice(0,f.length),void 0}if("function"==typeof a?(f=c(this),b=a):f=d(this,a),!b)return f.splice(0,f.length),void 0;for(h=0,i=f.length;i>h;++h)if(f[h].listener===b)return f.splice(h,1),void 0},f.once=function(a,b,c){var d=function(){f.off(a,d),b.apply(this,arguments)};f.on(a,d,c)},f.bind=function(a,b){var c,d,e;if(b)for(d=0,e=b.length;e>d;++d){if("function"!=typeof a[b[d]])throw new Error("No such method "+b[d]);this.on(b[d],a[b[d]],a)}else for(c in a)"function"==typeof a[c]&&this.on(c,a[c],a);return a},f.emit=function(a){var b,e,f=c(this),i=g.call(arguments);for(b=0,e=f.length;e>b;++b)h(a,f[b],i);for(f=d(this,a).slice(),i=g.call(arguments,1),b=0,e=f.length;e>b;++b)h(a,f[b],i)},f.errback=function(a){this.errbacks||(this.errbacks=[]),this.errbacks.push(b(a))},f}var g=Array.prototype.slice;return{createEventEmitter:f}}),"undefined"!=typeof window&&(window.define=function(a){try{delete window.define}catch(b){window.define=void 0}window.when=a()},window.define.amd={}),function(a,b){"use strict";a(function(a){function c(a,b,c,d){return e(a).then(b,c,d)}function d(a,b){this._message=a,this.inspect=b}function e(a){return h(function(b){b(a)})}function f(a){return c(a,k)}function g(){function a(a,f,g){b.resolve=b.resolver.resolve=function(b){return d?e(b):(d=!0,a(b),c)},b.reject=b.resolver.reject=function(a){return d?e(k(a)):(d=!0,f(a),c)},b.notify=b.resolver.notify=function(a){return g(a),a}}var b,c,d;return b={promise:S,resolve:S,reject:S,notify:S,resolver:{resolve:S,reject:S,notify:S}},b.promise=c=h(a),b}function h(a){return i(a,Q.PromiseStatus&&Q.PromiseStatus())}function i(a,b){function c(a,b,c,d){function e(e){e._message(a,b,c,d)}l?l.push(e):E(function(){e(j)})}function e(){return j?j.inspect():D()}function f(a){l&&(j=n(a),q(l,j),l=S,b&&r(j,b))}function g(a){f(k(a))}function h(a){l&&q(l,m(a))}var i,j,l=[];i=new d(c,e),i._status=b;try{a(f,g,h)}catch(o){g(o)}return i}function j(a){return l(new o(a),function(){return B(a)})}function k(a){return l(new p(a),function(){return C(a)})}function l(a,b){return new d(function(b,c,d){try{d(a[b].apply(a,c))}catch(e){d(k(e))}},b)}function m(a){return new d(function(b,c,d,e){var f=c[2];try{e("function"==typeof f?f(a):a)}catch(g){e(g)}})}function n(a){return a instanceof d?a:a===Object(a)&&"then"in a?h(function(b,c,d){E(function(){try{var e=a.then;"function"==typeof e?J(e,a,b,c,d):b(j(a))}catch(f){c(f)}})}):j(a)}function o(a){this.value=a}function p(a){this.reason=a}function q(a,b){E(function(){for(var c,d=0;c=a[d++];)c(b)})}function r(a,b){function c(){b.fulfilled()}function d(a){b.rejected(a)}a.then(c,d)}function s(a){return a&&"function"==typeof a.then}function t(a,b,d,e,f){return c(a,function(a){function g(d,e,f){function g(a){n(a)}function h(a){m(a)}var i,j,k,l,m,n,o,p;if(o=a.length>>>0,i=Math.max(0,Math.min(b,o)),k=[],j=o-i+1,l=[],i)for(n=function(a){l.push(a),--j||(m=n=G,e(l))},m=function(a){k.push(a),--i||(m=n=G,d(k))},p=0;o>p;++p)p in a&&c(a[p],h,g,f);else d(k)}return h(g).then(d,e,f)})}function u(a,b,c,d){function e(a){return b?b(a[0]):a[0]}return t(a,1,e,c,d)}function v(a,b,c,d){return z(a,G).then(b,c,d)}function w(){return z(arguments,G)}function x(a){return z(a,B,C)}function y(a,b){return z(a,b)}function z(a,b,d){return c(a,function(a){function e(e,f,g){function h(a,h){c(a,b,d).then(function(a){i[h]=a,g(a),--k||e(i)},f)}var i,j,k,l;if(k=j=a.length>>>0,i=[],!k)return e(i),void 0;for(l=0;j>l;l++)l in a?h(a[l],l):--k}return i(e)})}function A(a,b){var d=J(I,arguments,1);return c(a,function(a){var e;return e=a.length,d[0]=function(a,d,f){return c(a,function(a){return c(d,function(c){return b(a,c,f,e)})})},H.apply(a,d)})}function B(a){return{state:"fulfilled",value:a}}function C(a){return{state:"rejected",reason:a}}function D(){return{state:"pending"}}function E(a){1===L.push(a)&&K(F)}function F(){for(var a,b=0;a=L[b++];)a();L=[]}function G(a){return a}c.promise=h,c.resolve=e,c.reject=f,c.defer=g,c.join=w,c.all=v,c.map=y,c.reduce=A,c.settle=x,c.any=u,c.some=t,c.isPromise=s,c.isPromiseLike=s,d.prototype={then:function(){var a,b;return a=arguments,b=this._message,i(function(c,d,e){b("when",a,c,e)},this._status&&this._status.observed())},otherwise:function(a){return this.then(S,a)},ensure:function(a){function b(){return e(a())}return this.then(b,b).yield(this)},yield:function(a){return this.then(function(){return a})},tap:function(a){return this.then(a).yield(this)},spread:function(a){return this.then(function(b){return v(b,function(b){return a.apply(S,b)})})},always:function(a,b){return this.then(a,a,b)}},o.prototype.when=function(a){return"function"==typeof a?a(this.value):this.value},p.prototype.when=function(a,b){if("function"==typeof b)return b(this.reason);throw this.reason};var H,I,J,K,L,M,N,O,P,Q,R,S;if(R=a,L=[],M=b.setTimeout,Q="undefined"!=typeof console?console:c,"function"==typeof setImmediate)K=setImmediate.bind(b);else if("undefined"!=typeof MessageChannel){var T=new MessageChannel;T.port1.onmessage=F,K=function(){T.port2.postMessage(0)}}else if("object"==typeof process&&process.nextTick)K=process.nextTick;else try{K=R("vertx").runOnLoop||R("vertx").runOnContext}catch(U){K=function(a){M(a,0)}}return N=Function.prototype,O=N.call,J=N.bind?O.bind(O):function(a,b){return a.apply(b,I.call(arguments,2))},P=[],I=P.slice,H=P.reduce||function(a){var b,c,d,e,f;if(f=0,b=Object(this),e=b.length>>>0,c=arguments,c.length<=1)for(;;){if(f in b){d=b[f++];break}if(++f>=e)throw new TypeError}else d=c[1];for(;e>f;++f)f in b&&(d=a(d,b[f],f,b));return d},c})}("function"==typeof define&&define.amd?define:function(a){module.exports=a(require)},this),"object"==typeof module&&"function"==typeof require)var bane=require("bane"),websocket=require("faye-websocket"),when=require("when");Mopidy.WebSocket="object"==typeof module&&"function"==typeof require?websocket.Client:window.WebSocket,Mopidy.prototype._configure=function(a){var b="undefined"!=typeof document&&document.location.host||"localhost";return a.webSocketUrl=a.webSocketUrl||"ws://"+b+"/mopidy/ws/",a.autoConnect!==!1&&(a.autoConnect=!0),a.backoffDelayMin=a.backoffDelayMin||1e3,a.backoffDelayMax=a.backoffDelayMax||64e3,a},Mopidy.prototype._getConsole=function(){var a="undefined"!=typeof a&&a||{};return a.log=a.log||function(){},a.warn=a.warn||function(){},a.error=a.error||function(){},a},Mopidy.prototype._delegateEvents=function(){this.off("websocket:close"),this.off("websocket:error"),this.off("websocket:incomingMessage"),this.off("websocket:open"),this.off("state:offline"),this.on("websocket:close",this._cleanup),this.on("websocket:error",this._handleWebSocketError),this.on("websocket:incomingMessage",this._handleMessage),this.on("websocket:open",this._resetBackoffDelay),this.on("websocket:open",this._getApiSpec),this.on("state:offline",this._reconnect)},Mopidy.prototype.connect=function(){if(this._webSocket){if(this._webSocket.readyState===Mopidy.WebSocket.OPEN)return;this._webSocket.close()}this._webSocket=this._settings.webSocket||new Mopidy.WebSocket(this._settings.webSocketUrl),this._webSocket.onclose=function(a){this.emit("websocket:close",a)}.bind(this),this._webSocket.onerror=function(a){this.emit("websocket:error",a)}.bind(this),this._webSocket.onopen=function(){this.emit("websocket:open")}.bind(this),this._webSocket.onmessage=function(a){this.emit("websocket:incomingMessage",a)}.bind(this)},Mopidy.prototype._cleanup=function(a){Object.keys(this._pendingRequests).forEach(function(b){var c=this._pendingRequests[b];delete this._pendingRequests[b],c.reject({message:"WebSocket closed",closeEvent:a})}.bind(this)),this.emit("state:offline")},Mopidy.prototype._reconnect=function(){this.emit("reconnectionPending",{timeToAttempt:this._backoffDelay}),setTimeout(function(){this.emit("reconnecting"),this.connect()}.bind(this),this._backoffDelay),this._backoffDelay=2*this._backoffDelay,this._backoffDelay>this._settings.backoffDelayMax&&(this._backoffDelay=this._settings.backoffDelayMax)},Mopidy.prototype._resetBackoffDelay=function(){this._backoffDelay=this._settings.backoffDelayMin},Mopidy.prototype.close=function(){this.off("state:offline",this._reconnect),this._webSocket.close()},Mopidy.prototype._handleWebSocketError=function(a){this._console.warn("WebSocket error:",a.stack||a)},Mopidy.prototype._send=function(a){var b=when.defer();switch(this._webSocket.readyState){case Mopidy.WebSocket.CONNECTING:b.resolver.reject({message:"WebSocket is still connecting"});break;case Mopidy.WebSocket.CLOSING:b.resolver.reject({message:"WebSocket is closing"});break;case Mopidy.WebSocket.CLOSED:b.resolver.reject({message:"WebSocket is closed"});break;default:a.jsonrpc="2.0",a.id=this._nextRequestId(),this._pendingRequests[a.id]=b.resolver,this._webSocket.send(JSON.stringify(a)),this.emit("websocket:outgoingMessage",a)}return b.promise},Mopidy.prototype._nextRequestId=function(){var a=-1;return function(){return a+=1}}(),Mopidy.prototype._handleMessage=function(a){try{var b=JSON.parse(a.data);b.hasOwnProperty("id")?this._handleResponse(b):b.hasOwnProperty("event")?this._handleEvent(b):this._console.warn("Unknown message type received. Message was: "+a.data)}catch(c){if(!(c instanceof SyntaxError))throw c;this._console.warn("WebSocket message parsing failed. Message was: "+a.data)}},Mopidy.prototype._handleResponse=function(a){if(!this._pendingRequests.hasOwnProperty(a.id))return this._console.warn("Unexpected response received. Message was:",a),void 0;var b=this._pendingRequests[a.id];delete this._pendingRequests[a.id],a.hasOwnProperty("result")?b.resolve(a.result):a.hasOwnProperty("error")?(b.reject(a.error),this._console.warn("Server returned error:",a.error)):(b.reject({message:"Response without 'result' or 'error' received",data:{response:a}}),this._console.warn("Response without 'result' or 'error' received. Message was:",a))},Mopidy.prototype._handleEvent=function(a){var b=a.event,c=a;delete c.event,this.emit("event:"+this._snakeToCamel(b),c)},Mopidy.prototype._getApiSpec=function(){return this._send({method:"core.describe"}).then(this._createApi.bind(this),this._handleWebSocketError).then(null,this._handleWebSocketError)},Mopidy.prototype._createApi=function(a){var b=function(a){return function(){var b=Array.prototype.slice.call(arguments);return this._send({method:a,params:b})}.bind(this)}.bind(this),c=function(a){var b=a.split(".");return b.length>=1&&"core"===b[0]&&(b=b.slice(1)),b},d=function(a){var b=this;return a.forEach(function(a){a=this._snakeToCamel(a),b[a]=b[a]||{},b=b[a]}.bind(this)),b}.bind(this),e=function(e){var f=c(e),g=this._snakeToCamel(f.slice(-1)[0]),h=d(f.slice(0,-1));h[g]=b(e),h[g].description=a[e].description,h[g].params=a[e].params}.bind(this);Object.keys(a).forEach(e),this.emit("state:online")},Mopidy.prototype._snakeToCamel=function(a){return a.replace(/(_[a-z])/g,function(a){return a.toUpperCase().replace("_","")})},"object"==typeof exports&&(exports.Mopidy=Mopidy); \ No newline at end of file +!function(a){"object"==typeof exports?module.exports=a():"function"==typeof define&&define.amd?define(a):"undefined"!=typeof window?window.Mopidy=a():"undefined"!=typeof global?global.Mopidy=a():"undefined"!=typeof self&&(self.Mopidy=a())}(function(){var a;return function b(a,c,d){function e(g,h){if(!c[g]){if(!a[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);throw new Error("Cannot find module '"+g+"'")}var j=c[g]={exports:{}};a[g][0].call(j.exports,function(b){var c=a[g][1][b];return e(c?c:b)},j,j.exports,b,a,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;g0)for(d=0;e>d;++d)c[d](a,b);else setTimeout(function(){throw b.message=a+" listener threw error: "+b.message,b},0)}function b(a){if("function"!=typeof a)throw new TypeError("Listener is not function");return a}function c(a){return a.supervisors||(a.supervisors=[]),a.supervisors}function d(a,b){return a.listeners||(a.listeners={}),b&&!a.listeners[b]&&(a.listeners[b]=[]),b?a.listeners[b]:a.listeners}function e(a){return a.errbacks||(a.errbacks=[]),a.errbacks}function f(f){function h(b,c,d){try{c.listener.apply(c.thisp||f,d)}catch(g){a(b,g,e(f))}}return f=f||{},f.on=function(a,e,f){return"function"==typeof a?c(this).push({listener:a,thisp:e}):(d(this,a).push({listener:b(e),thisp:f}),void 0)},f.off=function(a,b){var f,g,h,i;if(!a){f=c(this),f.splice(0,f.length),g=d(this);for(h in g)g.hasOwnProperty(h)&&(f=d(this,h),f.splice(0,f.length));return f=e(this),f.splice(0,f.length),void 0}if("function"==typeof a?(f=c(this),b=a):f=d(this,a),!b)return f.splice(0,f.length),void 0;for(h=0,i=f.length;i>h;++h)if(f[h].listener===b)return f.splice(h,1),void 0},f.once=function(a,b,c){var d=function(){f.off(a,d),b.apply(this,arguments)};f.on(a,d,c)},f.bind=function(a,b){var c,d,e;if(b)for(d=0,e=b.length;e>d;++d){if("function"!=typeof a[b[d]])throw new Error("No such method "+b[d]);this.on(b[d],a[b[d]],a)}else for(c in a)"function"==typeof a[c]&&this.on(c,a[c],a);return a},f.emit=function(a){var b,e,f=c(this),i=g.call(arguments);for(b=0,e=f.length;e>b;++b)h(a,f[b],i);for(f=d(this,a).slice(),i=g.call(arguments,1),b=0,e=f.length;e>b;++b)h(a,f[b],i)},f.errback=function(a){this.errbacks||(this.errbacks=[]),this.errbacks.push(b(a))},f}var g=Array.prototype.slice;return{createEventEmitter:f}})},{}],3:[function(a,b){var c=b.exports={};c.nextTick=function(){var a="undefined"!=typeof window&&window.setImmediate,b="undefined"!=typeof window&&window.postMessage&&window.addEventListener;if(a)return function(a){return window.setImmediate(a)};if(b){var c=[];return window.addEventListener("message",function(a){if(a.source===window&&"process-tick"===a.data&&(a.stopPropagation(),c.length>0)){var b=c.shift();b()}},!0),function(a){c.push(a),window.postMessage("process-tick","*")}}return function(a){setTimeout(a,0)}}(),c.title="browser",c.browser=!0,c.env={},c.argv=[],c.binding=function(){throw new Error("process.binding is not supported")},c.cwd=function(){return"/"},c.chdir=function(){throw new Error("process.chdir is not supported")}},{}],4:[function(b,c){var d=b("__browserify_process");!function(a){"use strict";a(function(a){function b(a,b,d,e){return c(a).then(b,d,e)}function c(a){return a instanceof e?a:f(a)}function e(a,b){this._message=a,this.inspect=b}function f(a){return i(function(b){b(a)})}function g(a){return b(a,m)}function h(){function a(a,e,g){b.resolve=b.resolver.resolve=function(b){return d?f(b):(d=!0,a(b),c)},b.reject=b.resolver.reject=function(a){return d?f(m(a)):(d=!0,e(a),c)},b.notify=b.resolver.notify=function(a){return g(a),a}}var b,c,d;return b={promise:X,resolve:X,reject:X,notify:X,resolver:{resolve:X,reject:X,notify:X}},b.promise=c=i(a),b}function i(a){return j(a,T.PromiseStatus&&T.PromiseStatus())}function j(a,b){function c(a,b,c,d){function e(e){e._message(a,b,c,d)}l?l.push(e):G(function(){e(j)})}function d(){return j?j.inspect():F()}function f(a){if(l){var c=l;l=X,G(function(){j=p(i,a),b&&t(j,b),k(c,j)})}}function g(a){f(m(a))}function h(a){if(l){var b=l;G(function(){k(b,o(a))})}}var i,j,l=[];i=new e(c,d),i._status=b;try{a(f,g,h)}catch(n){g(n)}return i}function k(a,b){for(var c=0;c>>0,i=Math.max(0,Math.min(c,o)),k=[],j=o-i+1,l=[],i)for(n=function(a){l.push(a),--j||(m=n=I,e(l))},m=function(a){k.push(a),--i||(m=n=I,d(k))},p=0;o>p;++p)p in a&&b(a[p],h,g,f);else d(k)}return i(g).then(d,e,f)})}function w(a,b,c,d){function e(a){return b?b(a[0]):a[0]}return v(a,1,e,c,d)}function x(a,b,c,d){return B(a,I).then(b,c,d)}function y(){return B(arguments,I)}function z(a){return B(a,D,E)}function A(a,b){return B(a,b)}function B(a,c,d){return b(a,function(a){function e(e,f,g){function h(a,h){b(a,c,d).then(function(a){i[h]=a,--k||e(i)},f,g)}var i,j,k,l;if(k=j=a.length>>>0,i=[],!k)return e(i),void 0;for(l=0;j>l;l++)l in a?h(a[l],l):--k}return j(e)})}function C(a,c){var d=N(M,arguments,1);return b(a,function(a){var e;return e=a.length,d[0]=function(a,d,f){return b(a,function(a){return b(d,function(b){return c(a,b,f,e)})})},L.apply(a,d)})}function D(a){return{state:"fulfilled",value:a}}function E(a){return{state:"rejected",reason:a}}function F(){return{state:"pending"}}function G(a){1===P.push(a)&&O(H)}function H(){k(P),P=[]}function I(a){return a}function J(a){throw"function"==typeof T.reportUnhandled?T.reportUnhandled():G(function(){throw a}),a}b.promise=i,b.resolve=f,b.reject=g,b.defer=h,b.join=y,b.all=x,b.map=A,b.reduce=C,b.settle=z,b.any=w,b.some=v,b.isPromise=u,b.isPromiseLike=u;var K=e.prototype;K.then=function(){var a,b;return a=arguments,b=this._message,j(function(c,d,e){b("when",a,c,e)},this._status&&this._status.observed())},K["catch"]=K.otherwise=function(a){return this.then(X,a)},K["finally"]=K.ensure=function(a){function b(){return f(a())}return"function"==typeof a?this.then(b,b).yield(this):this},K.done=function(a,b){this.then(a,b).otherwise(J)},K.yield=function(a){return this.then(function(){return a})},K.tap=function(a){return this.then(a).yield(this)},K.spread=function(a){return this.then(function(b){return x(b,function(b){return a.apply(X,b)})})},K.always=function(a,b){return this.then(a,a,b)},r.prototype.when=function(a){return"function"==typeof a?a(this.value):this.value},s.prototype.when=function(a,b){if("function"==typeof b)return b(this.reason);throw this.reason};var L,M,N,O,P,Q,R,S,T,U,V,W,X;if(V=a,P=[],T="undefined"!=typeof console?console:b,"object"==typeof d&&d.nextTick)O=d.nextTick;else if(W="function"==typeof MutationObserver&&MutationObserver||"function"==typeof WebKitMutationObserver&&WebKitMutationObserver)O=function(a,b,c){var d=a.createElement("div");return new b(c).observe(d,{attributes:!0}),function(){d.setAttribute("x","x")}}(document,W,H);else try{O=V("vertx").runOnLoop||V("vertx").runOnContext}catch(Y){U=setTimeout,O=function(a){U(a,0)}}return Q=Function.prototype,R=Q.call,N=Q.bind?R.bind(R):function(a,b){return a.apply(b,M.call(arguments,2))},S=[],M=S.slice,L=S.reduce||function(a){var b,c,d,e,f;if(f=0,b=Object(this),e=b.length>>>0,c=arguments,c.length<=1)for(;;){if(f in b){d=b[f++];break}if(++f>=e)throw new TypeError}else d=c[1];for(;e>f;++f)f in b&&(d=a(d,b[f],f,b));return d},b})}("function"==typeof a&&a.amd?a:function(a){c.exports=a(b)})},{__browserify_process:3}],5:[function(a,b){function c(a){return this instanceof c?(this._settings=this._configure(a||{}),this._console=this._getConsole(),this._backoffDelay=this._settings.backoffDelayMin,this._pendingRequests={},this._webSocket=null,d.createEventEmitter(this),this._delegateEvents(),this._settings.autoConnect&&this.connect(),void 0):new c(a)}var d=a("bane"),e=a("../lib/websocket/"),f=a("when");c.WebSocket=e.Client,c.prototype._configure=function(a){var b="undefined"!=typeof document&&document.location.host||"localhost";return a.webSocketUrl=a.webSocketUrl||"ws://"+b+"/mopidy/ws/",a.autoConnect!==!1&&(a.autoConnect=!0),a.backoffDelayMin=a.backoffDelayMin||1e3,a.backoffDelayMax=a.backoffDelayMax||64e3,a},c.prototype._getConsole=function(){var a="undefined"!=typeof a&&a||{};return a.log=a.log||function(){},a.warn=a.warn||function(){},a.error=a.error||function(){},a},c.prototype._delegateEvents=function(){this.off("websocket:close"),this.off("websocket:error"),this.off("websocket:incomingMessage"),this.off("websocket:open"),this.off("state:offline"),this.on("websocket:close",this._cleanup),this.on("websocket:error",this._handleWebSocketError),this.on("websocket:incomingMessage",this._handleMessage),this.on("websocket:open",this._resetBackoffDelay),this.on("websocket:open",this._getApiSpec),this.on("state:offline",this._reconnect)},c.prototype.connect=function(){if(this._webSocket){if(this._webSocket.readyState===c.WebSocket.OPEN)return;this._webSocket.close()}this._webSocket=this._settings.webSocket||new c.WebSocket(this._settings.webSocketUrl),this._webSocket.onclose=function(a){this.emit("websocket:close",a)}.bind(this),this._webSocket.onerror=function(a){this.emit("websocket:error",a)}.bind(this),this._webSocket.onopen=function(){this.emit("websocket:open")}.bind(this),this._webSocket.onmessage=function(a){this.emit("websocket:incomingMessage",a)}.bind(this)},c.prototype._cleanup=function(a){Object.keys(this._pendingRequests).forEach(function(b){var c=this._pendingRequests[b];delete this._pendingRequests[b],c.reject({message:"WebSocket closed",closeEvent:a})}.bind(this)),this.emit("state:offline")},c.prototype._reconnect=function(){this.emit("reconnectionPending",{timeToAttempt:this._backoffDelay}),setTimeout(function(){this.emit("reconnecting"),this.connect()}.bind(this),this._backoffDelay),this._backoffDelay=2*this._backoffDelay,this._backoffDelay>this._settings.backoffDelayMax&&(this._backoffDelay=this._settings.backoffDelayMax)},c.prototype._resetBackoffDelay=function(){this._backoffDelay=this._settings.backoffDelayMin},c.prototype.close=function(){this.off("state:offline",this._reconnect),this._webSocket.close()},c.prototype._handleWebSocketError=function(a){this._console.warn("WebSocket error:",a.stack||a)},c.prototype._send=function(a){var b=f.defer();switch(this._webSocket.readyState){case c.WebSocket.CONNECTING:b.resolver.reject({message:"WebSocket is still connecting"});break;case c.WebSocket.CLOSING:b.resolver.reject({message:"WebSocket is closing"});break;case c.WebSocket.CLOSED:b.resolver.reject({message:"WebSocket is closed"});break;default:a.jsonrpc="2.0",a.id=this._nextRequestId(),this._pendingRequests[a.id]=b.resolver,this._webSocket.send(JSON.stringify(a)),this.emit("websocket:outgoingMessage",a)}return b.promise},c.prototype._nextRequestId=function(){var a=-1;return function(){return a+=1}}(),c.prototype._handleMessage=function(a){try{var b=JSON.parse(a.data);b.hasOwnProperty("id")?this._handleResponse(b):b.hasOwnProperty("event")?this._handleEvent(b):this._console.warn("Unknown message type received. Message was: "+a.data)}catch(c){if(!(c instanceof SyntaxError))throw c;this._console.warn("WebSocket message parsing failed. Message was: "+a.data)}},c.prototype._handleResponse=function(a){if(!this._pendingRequests.hasOwnProperty(a.id))return this._console.warn("Unexpected response received. Message was:",a),void 0;var b=this._pendingRequests[a.id];delete this._pendingRequests[a.id],a.hasOwnProperty("result")?b.resolve(a.result):a.hasOwnProperty("error")?(b.reject(a.error),this._console.warn("Server returned error:",a.error)):(b.reject({message:"Response without 'result' or 'error' received",data:{response:a}}),this._console.warn("Response without 'result' or 'error' received. Message was:",a))},c.prototype._handleEvent=function(a){var b=a.event,c=a;delete c.event,this.emit("event:"+this._snakeToCamel(b),c)},c.prototype._getApiSpec=function(){return this._send({method:"core.describe"}).then(this._createApi.bind(this),this._handleWebSocketError).then(null,this._handleWebSocketError)},c.prototype._createApi=function(a){var b=function(a){return function(){var b=Array.prototype.slice.call(arguments);return this._send({method:a,params:b})}.bind(this)}.bind(this),c=function(a){var b=a.split(".");return b.length>=1&&"core"===b[0]&&(b=b.slice(1)),b},d=function(a){var b=this;return a.forEach(function(a){a=this._snakeToCamel(a),b[a]=b[a]||{},b=b[a]}.bind(this)),b}.bind(this),e=function(e){var f=c(e),g=this._snakeToCamel(f.slice(-1)[0]),h=d(f.slice(0,-1));h[g]=b(e),h[g].description=a[e].description,h[g].params=a[e].params}.bind(this);Object.keys(a).forEach(e),this.emit("state:online")},c.prototype._snakeToCamel=function(a){return a.replace(/(_[a-z])/g,function(a){return a.toUpperCase().replace("_","")})},b.exports=c},{"../lib/websocket/":1,bane:2,when:4}]},{},[5])(5)}); \ No newline at end of file From a83b71239bba9216e51e724ffb12f3c4d931ce0e Mon Sep 17 00:00:00 2001 From: Paul Connolley Date: Tue, 17 Dec 2013 08:57:44 +0000 Subject: [PATCH 02/24] Update test so that it correctly requires the mopidy module As part of issue #609, the require statement in mopidy-test.js should have been updated as the API to require mopidy has changed from: require('mopidy').Mopidy; to: require('mopidy'); --- js/test/mopidy-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/test/mopidy-test.js b/js/test/mopidy-test.js index 0bf97f60..ee34d845 100644 --- a/js/test/mopidy-test.js +++ b/js/test/mopidy-test.js @@ -2,7 +2,7 @@ if (typeof module === "object" && typeof require === "function") { var buster = require("buster"); - var Mopidy = require("../src/mopidy").Mopidy; + var Mopidy = require("../src/mopidy"); var when = require("when"); } From 26b8490672167e1528cf2559630d1a6477e9725b Mon Sep 17 00:00:00 2001 From: Paul Connolley Date: Tue, 17 Dec 2013 16:52:35 +0000 Subject: [PATCH 03/24] Updated the test process for mopidy.js Following on from the previous issue #609 commits, I have updated the build process to cater to the fact that the files are no longer available to test in the browser environment. 2 new browserify tasks build the mopidy.js file and then when.js file (available in node_modules.) These files are placed in test/lib/ (This directory has been added to the .gitignore file) prior to the running of the buster tests. As these files are ignored in the .gitignore, this will prevent them from being committed to git and also prevent them from being packaged up to npm. Once the tests have completed, the main browserify task will run to build the official browser release. --- .gitignore | 1 + js/Gruntfile.js | 24 ++++++++++++++++++++++-- js/buster.js | 12 +----------- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/.gitignore b/.gitignore index 79230110..1ec12cbc 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ node_modules/ nosetests.xml *~ *.orig +js/test/lib/ diff --git a/js/Gruntfile.js b/js/Gruntfile.js index f59ed9f8..df0145ab 100644 --- a/js/Gruntfile.js +++ b/js/Gruntfile.js @@ -20,6 +20,25 @@ module.exports = function (grunt) { all: {} }, browserify: { + test_mopidy: { + files: { + "test/lib/mopidy.js": "<%= meta.files.main %>" + }, + options: { + postBundleCB: function (err, src, next) { + next(null, grunt.template.process("<%= meta.banner %>") + src); + }, + standalone: "Mopidy" + } + }, + test_when: { + files: { + "test/lib/when.js": "node_modules/when/when.js" + }, + options: { + standalone: "when" + } + }, dist: { files: { "<%= meta.files.concat %>": "<%= meta.files.main %>" @@ -68,8 +87,9 @@ module.exports = function (grunt) { } }); - grunt.registerTask("test", ["jshint", "buster"]); - grunt.registerTask("build", ["test", "browserify", "uglify"]); + grunt.registerTask("test_build", ["browserify:test_when", "browserify:test_mopidy"]); + grunt.registerTask("test", ["jshint", "test_build", "buster"]); + grunt.registerTask("build", ["test", "browserify:dist", "uglify"]); grunt.registerTask("default", ["build"]); grunt.loadNpmTasks("grunt-buster"); diff --git a/js/buster.js b/js/buster.js index 1cc517c8..c5dec850 100644 --- a/js/buster.js +++ b/js/buster.js @@ -2,23 +2,13 @@ var config = module.exports; config.browser_tests = { environment: "browser", - libs: [ - "lib/bane-*.js", - "lib/when-define-shim.js", - "lib/when-*.js" - ], - sources: ["src/**/*.js"], + libs: ["test/lib/*.js"], testHelpers: ["test/**/*-helper.js"], tests: ["test/**/*-test.js"] }; config.node_tests = { environment: "node", - libs: [ - "lib/bane-*.js", - "lib/when-define-shim.js", - "lib/when-*.js" - ], sources: ["src/**/*.js"], testHelpers: ["test/**/*-helper.js"], tests: ["test/**/*-test.js"] From 79e0fe6d87e7e586e0d814bac8e6b65578561a7a Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 31 Dec 2013 16:31:45 +0100 Subject: [PATCH 04/24] docs: Align header lines --- docs/modules/mpd.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/modules/mpd.rst b/docs/modules/mpd.rst index 85cb2789..4a9eb7e8 100644 --- a/docs/modules/mpd.rst +++ b/docs/modules/mpd.rst @@ -1,6 +1,6 @@ -***************************************** +******************************* :mod:`mopidy.mpd` -- MPD server -***************************************** +******************************* For details on how to use Mopidy's MPD server, see :ref:`ext-mpd`. From a9ab02737c48420841630e8b9db1c98ad7264276 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 31 Dec 2013 16:32:58 +0100 Subject: [PATCH 05/24] docs: Remove outdated links to issue labels --- docs/ext/local.rst | 6 ------ docs/ext/mpd.rst | 6 ------ docs/ext/stream.rst | 6 ------ 3 files changed, 18 deletions(-) diff --git a/docs/ext/local.rst b/docs/ext/local.rst index cbde826f..0c16142c 100644 --- a/docs/ext/local.rst +++ b/docs/ext/local.rst @@ -9,12 +9,6 @@ Extension for playing music from a local music archive. This backend handles URIs starting with ``local:``. -Known issues -============ - -https://github.com/mopidy/mopidy/issues?labels=Local+backend - - Dependencies ============ diff --git a/docs/ext/mpd.rst b/docs/ext/mpd.rst index 46a01715..fa91f6a2 100644 --- a/docs/ext/mpd.rst +++ b/docs/ext/mpd.rst @@ -15,12 +15,6 @@ compatible with clients for the original MPD server. For more details on our MPD server implementation, see :mod:`mopidy.mpd`. -Known issues -============ - -https://github.com/mopidy/mopidy/issues?labels=MPD+frontend - - Limitations =========== diff --git a/docs/ext/stream.rst b/docs/ext/stream.rst index 30bc22ab..22e7d99e 100644 --- a/docs/ext/stream.rst +++ b/docs/ext/stream.rst @@ -11,12 +11,6 @@ The stream backend will handle streaming of URIs matching the are installed. -Known issues -============ - -https://github.com/mopidy/mopidy/issues?labels=Stream+backend - - Dependencies ============ From 9eb6307607f94d7bb0980eb605f21cfe0f140a5f Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 31 Dec 2013 16:36:04 +0100 Subject: [PATCH 06/24] docs: Fix broken label reference --- docs/changelog.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 56177441..3898e5f3 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1701,8 +1701,8 @@ to this problem. - Local backend: - Add :command:`mopidy-scan` command to generate ``tag_cache`` files without - any help from the original MPD server. See :ref:`generating-a-tag-cache` - for instructions on how to use it. + any help from the original MPD server. See + :ref:`generating-a-local-library` for instructions on how to use it. - Fix support for UTF-8 encoding in tag caches. From ed444a9abac9b2ab783e0838626702aa732b4b1e Mon Sep 17 00:00:00 2001 From: lukegiuliani Date: Wed, 1 Jan 2014 22:54:47 +1100 Subject: [PATCH 07/24] Add in additional detail for Gentoo installs Add in details around the fact that on Gentoo gstreamer-0.10 is in a slot, which is completely non-obvious the first time around. --- docs/installation/index.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/installation/index.rst b/docs/installation/index.rst index 2dc1d5c6..ad52f832 100644 --- a/docs/installation/index.rst +++ b/docs/installation/index.rst @@ -193,6 +193,18 @@ can install Mopidy from PyPI using Pip. sudo yum install -y python-gst0.10 gstreamer0.10-plugins-good \ gstreamer0.10-plugins-ugly gstreamer0.10-tools + If you use Gentoo you need to be careful because gstreamer 0.10 is in + a different (lower) slot than 1.0, the default. Your emerge commands + will need to include the slot:: + + emerge -av gst-python gst-plugins-bad:0.10 gst-plugins-good:0.10 \ + gst-plugins-ugly:0.10 gst-plugins-meta:0.10 + + gst-plugins-meta:0.10 is the one that actually pulls in the plugins + you want, so pay attention to the use flags (e.g., alsa, mp3) + + + #. Optional: If you want Spotify support in Mopidy, you'll need to install libspotify and the Python bindings, pyspotify. From 8399e1f1b28507d251fd65f16e30ef561dbe1c48 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Wed, 1 Jan 2014 13:11:04 +0100 Subject: [PATCH 08/24] docs: Formatting --- docs/installation/index.rst | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/docs/installation/index.rst b/docs/installation/index.rst index ad52f832..456ae73a 100644 --- a/docs/installation/index.rst +++ b/docs/installation/index.rst @@ -193,17 +193,15 @@ can install Mopidy from PyPI using Pip. sudo yum install -y python-gst0.10 gstreamer0.10-plugins-good \ gstreamer0.10-plugins-ugly gstreamer0.10-tools - If you use Gentoo you need to be careful because gstreamer 0.10 is in - a different (lower) slot than 1.0, the default. Your emerge commands - will need to include the slot:: + If you use Gentoo you need to be careful because GStreamer 0.10 is in + a different lower slot than 1.0, the default. Your emerge commands will + need to include the slot:: emerge -av gst-python gst-plugins-bad:0.10 gst-plugins-good:0.10 \ gst-plugins-ugly:0.10 gst-plugins-meta:0.10 gst-plugins-meta:0.10 is the one that actually pulls in the plugins - you want, so pay attention to the use flags (e.g., alsa, mp3) - - + you want, so pay attention to the use flags, e.g. ``alsa``, ``mp3``, etc. #. Optional: If you want Spotify support in Mopidy, you'll need to install libspotify and the Python bindings, pyspotify. From 9e905e2e4bc7e1c42d079b17b960da2c66203551 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Wed, 1 Jan 2014 13:15:47 +0100 Subject: [PATCH 09/24] docs: Bump copyright year to 2014 --- docs/authors.rst | 2 +- docs/conf.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/authors.rst b/docs/authors.rst index 54ba95af..7c00e2ac 100644 --- a/docs/authors.rst +++ b/docs/authors.rst @@ -4,7 +4,7 @@ Authors ******* -Mopidy is copyright 2009-2013 Stein Magnus Jodal and contributors. Mopidy is +Mopidy is copyright 2009-2014 Stein Magnus Jodal and contributors. Mopidy is licensed under the `Apache License, Version 2.0 `_. diff --git a/docs/conf.py b/docs/conf.py index 5417a55c..bb9b7c2f 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -97,7 +97,7 @@ source_suffix = '.rst' master_doc = 'index' project = 'Mopidy' -copyright = '2009-2013, Stein Magnus Jodal and contributors' +copyright = '2009-2014, Stein Magnus Jodal and contributors' from mopidy.utils.versioning import get_version release = get_version() From 0fb7c795242bb2b6018041bb07b914b6b0aa604b Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Wed, 1 Jan 2014 13:31:20 +0100 Subject: [PATCH 10/24] log: Use loggers named after __name__ --- mopidy/__main__.py | 2 +- mopidy/audio/actor.py | 2 +- mopidy/audio/mixers/auto.py | 2 +- mopidy/backends/local/__init__.py | 2 +- mopidy/backends/local/actor.py | 2 +- mopidy/backends/local/commands.py | 2 +- mopidy/backends/local/json/actor.py | 2 +- mopidy/backends/local/json/library.py | 2 +- mopidy/backends/local/playback.py | 2 +- mopidy/backends/local/playlists.py | 2 +- mopidy/backends/local/translator.py | 2 +- mopidy/backends/stream/actor.py | 2 +- mopidy/commands.py | 2 +- mopidy/config/__init__.py | 2 +- mopidy/config/keyring.py | 2 +- mopidy/core/playback.py | 2 +- mopidy/core/tracklist.py | 2 +- mopidy/ext.py | 2 +- mopidy/http/actor.py | 2 +- mopidy/http/ws.py | 2 +- mopidy/listener.py | 2 +- mopidy/mpd/actor.py | 2 +- mopidy/mpd/dispatcher.py | 2 +- mopidy/mpd/session.py | 2 +- mopidy/utils/network.py | 2 +- mopidy/utils/path.py | 2 +- mopidy/utils/process.py | 2 +- mopidy/zeroconf.py | 2 +- 28 files changed, 28 insertions(+), 28 deletions(-) diff --git a/mopidy/__main__.py b/mopidy/__main__.py index 6a6c0eb8..2e6a6cc5 100644 --- a/mopidy/__main__.py +++ b/mopidy/__main__.py @@ -29,7 +29,7 @@ from mopidy import commands, ext from mopidy import config as config_lib from mopidy.utils import log, path, process, versioning -logger = logging.getLogger('mopidy.main') +logger = logging.getLogger(__name__) def main(): diff --git a/mopidy/audio/actor.py b/mopidy/audio/actor.py index 5c931865..feeee820 100644 --- a/mopidy/audio/actor.py +++ b/mopidy/audio/actor.py @@ -15,7 +15,7 @@ from . import mixers, playlists, utils from .constants import PlaybackState from .listener import AudioListener -logger = logging.getLogger('mopidy.audio') +logger = logging.getLogger(__name__) mixers.register_mixers() diff --git a/mopidy/audio/mixers/auto.py b/mopidy/audio/mixers/auto.py index 023674bf..7e59b602 100644 --- a/mopidy/audio/mixers/auto.py +++ b/mopidy/audio/mixers/auto.py @@ -12,7 +12,7 @@ import gst import logging -logger = logging.getLogger('mopidy.audio.mixers.auto') +logger = logging.getLogger(__name__) # TODO: we might want to add some ranking to the mixers we know about? diff --git a/mopidy/backends/local/__init__.py b/mopidy/backends/local/__init__.py index dedc868c..e9204338 100644 --- a/mopidy/backends/local/__init__.py +++ b/mopidy/backends/local/__init__.py @@ -7,7 +7,7 @@ import mopidy from mopidy import config, ext from mopidy.utils import encoding, path -logger = logging.getLogger('mopidy.backends.local') +logger = logging.getLogger(__name__) class Extension(ext.Extension): diff --git a/mopidy/backends/local/actor.py b/mopidy/backends/local/actor.py index a73f627e..a9902c8b 100644 --- a/mopidy/backends/local/actor.py +++ b/mopidy/backends/local/actor.py @@ -11,7 +11,7 @@ from mopidy.utils import encoding, path from .playlists import LocalPlaylistsProvider from .playback import LocalPlaybackProvider -logger = logging.getLogger('mopidy.backends.local') +logger = logging.getLogger(__name__) class LocalBackend(pykka.ThreadingActor, base.Backend): diff --git a/mopidy/backends/local/commands.py b/mopidy/backends/local/commands.py index 5e9b42e6..e9eb807c 100644 --- a/mopidy/backends/local/commands.py +++ b/mopidy/backends/local/commands.py @@ -10,7 +10,7 @@ from mopidy.utils import path from . import translator -logger = logging.getLogger('mopidy.backends.local.commands') +logger = logging.getLogger(__name__) class LocalCommand(commands.Command): diff --git a/mopidy/backends/local/json/actor.py b/mopidy/backends/local/json/actor.py index 66a6fbd5..4fc46417 100644 --- a/mopidy/backends/local/json/actor.py +++ b/mopidy/backends/local/json/actor.py @@ -10,7 +10,7 @@ from mopidy.utils import encoding from . import library -logger = logging.getLogger('mopidy.backends.local.json') +logger = logging.getLogger(__name__) class LocalJsonBackend(pykka.ThreadingActor, base.Backend): diff --git a/mopidy/backends/local/json/library.py b/mopidy/backends/local/json/library.py index 33427231..99640543 100644 --- a/mopidy/backends/local/json/library.py +++ b/mopidy/backends/local/json/library.py @@ -11,7 +11,7 @@ from mopidy import models from mopidy.backends import base from mopidy.backends.local import search -logger = logging.getLogger('mopidy.backends.local.json') +logger = logging.getLogger(__name__) def load_library(json_file): diff --git a/mopidy/backends/local/playback.py b/mopidy/backends/local/playback.py index ae8eeb82..6ef7b410 100644 --- a/mopidy/backends/local/playback.py +++ b/mopidy/backends/local/playback.py @@ -6,7 +6,7 @@ from mopidy.backends import base from . import translator -logger = logging.getLogger('mopidy.backends.local') +logger = logging.getLogger(__name__) class LocalPlaybackProvider(base.BasePlaybackProvider): diff --git a/mopidy/backends/local/playlists.py b/mopidy/backends/local/playlists.py index e8996b51..9e2459c6 100644 --- a/mopidy/backends/local/playlists.py +++ b/mopidy/backends/local/playlists.py @@ -12,7 +12,7 @@ from mopidy.utils import formatting, path from .translator import parse_m3u -logger = logging.getLogger('mopidy.backends.local') +logger = logging.getLogger(__name__) class LocalPlaylistsProvider(base.BasePlaylistsProvider): diff --git a/mopidy/backends/local/translator.py b/mopidy/backends/local/translator.py index 243eb314..8cc3df81 100644 --- a/mopidy/backends/local/translator.py +++ b/mopidy/backends/local/translator.py @@ -8,7 +8,7 @@ import urllib from mopidy.utils.encoding import locale_decode from mopidy.utils.path import path_to_uri, uri_to_path -logger = logging.getLogger('mopidy.backends.local') +logger = logging.getLogger(__name__) def local_track_uri_to_file_uri(uri, media_dir): diff --git a/mopidy/backends/stream/actor.py b/mopidy/backends/stream/actor.py index c807e09d..a5b2a539 100644 --- a/mopidy/backends/stream/actor.py +++ b/mopidy/backends/stream/actor.py @@ -10,7 +10,7 @@ from mopidy.audio import scan from mopidy.backends import base from mopidy.models import Track -logger = logging.getLogger('mopidy.backends.stream') +logger = logging.getLogger(__name__) class StreamBackend(pykka.ThreadingActor, base.Backend): diff --git a/mopidy/commands.py b/mopidy/commands.py index ba5a42f1..46989c9c 100644 --- a/mopidy/commands.py +++ b/mopidy/commands.py @@ -14,7 +14,7 @@ from mopidy.audio import Audio from mopidy.core import Core from mopidy.utils import deps, process, versioning -logger = logging.getLogger('mopidy.commands') +logger = logging.getLogger(__name__) _default_config = [] for base in glib.get_system_config_dirs() + (glib.get_user_config_dir(),): diff --git a/mopidy/config/__init__.py b/mopidy/config/__init__.py index f68567e7..d6400fad 100644 --- a/mopidy/config/__init__.py +++ b/mopidy/config/__init__.py @@ -12,7 +12,7 @@ from mopidy.config.schemas import * # noqa from mopidy.config.types import * # noqa from mopidy.utils import path, versioning -logger = logging.getLogger('mopidy.config') +logger = logging.getLogger(__name__) _logging_schema = ConfigSchema('logging') _logging_schema['console_format'] = String() diff --git a/mopidy/config/keyring.py b/mopidy/config/keyring.py index 169ffdd1..6800d2c4 100644 --- a/mopidy/config/keyring.py +++ b/mopidy/config/keyring.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals import logging -logger = logging.getLogger('mopidy.config.keyring') +logger = logging.getLogger(__name__) try: import dbus diff --git a/mopidy/core/playback.py b/mopidy/core/playback.py index 3c0e43fa..96d13017 100644 --- a/mopidy/core/playback.py +++ b/mopidy/core/playback.py @@ -8,7 +8,7 @@ from mopidy.audio import PlaybackState from . import listener -logger = logging.getLogger('mopidy.core') +logger = logging.getLogger(__name__) class PlaybackController(object): diff --git a/mopidy/core/tracklist.py b/mopidy/core/tracklist.py index d3cc0d75..816e7b65 100644 --- a/mopidy/core/tracklist.py +++ b/mopidy/core/tracklist.py @@ -9,7 +9,7 @@ from mopidy.models import TlTrack from . import listener -logger = logging.getLogger('mopidy.core') +logger = logging.getLogger(__name__) class TracklistController(object): diff --git a/mopidy/ext.py b/mopidy/ext.py index feadc99f..33b9497d 100644 --- a/mopidy/ext.py +++ b/mopidy/ext.py @@ -7,7 +7,7 @@ from mopidy import exceptions from mopidy import config as config_lib -logger = logging.getLogger('mopidy.ext') +logger = logging.getLogger(__name__) class Extension(object): diff --git a/mopidy/http/actor.py b/mopidy/http/actor.py index cc7c11a1..037fe1ea 100644 --- a/mopidy/http/actor.py +++ b/mopidy/http/actor.py @@ -14,7 +14,7 @@ from mopidy.core import CoreListener from . import ws -logger = logging.getLogger('mopidy.http') +logger = logging.getLogger(__name__) class HttpFrontend(pykka.ThreadingActor, CoreListener): diff --git a/mopidy/http/ws.py b/mopidy/http/ws.py index 5a0f2039..4d7aa9a2 100644 --- a/mopidy/http/ws.py +++ b/mopidy/http/ws.py @@ -9,7 +9,7 @@ from mopidy import core, models from mopidy.utils import jsonrpc -logger = logging.getLogger('mopidy.http') +logger = logging.getLogger(__name__) class WebSocketResource(object): diff --git a/mopidy/listener.py b/mopidy/listener.py index 715beb03..cce5556d 100644 --- a/mopidy/listener.py +++ b/mopidy/listener.py @@ -5,7 +5,7 @@ import logging import gobject import pykka -logger = logging.getLogger('mopidy.listener') +logger = logging.getLogger(__name__) def send_async(cls, event, **kwargs): diff --git a/mopidy/mpd/actor.py b/mopidy/mpd/actor.py index 9c33faaa..144b09d5 100644 --- a/mopidy/mpd/actor.py +++ b/mopidy/mpd/actor.py @@ -10,7 +10,7 @@ from mopidy.core import CoreListener from mopidy.mpd import session from mopidy.utils import encoding, network, process -logger = logging.getLogger('mopidy.mpd') +logger = logging.getLogger(__name__) class MpdFrontend(pykka.ThreadingActor, CoreListener): diff --git a/mopidy/mpd/dispatcher.py b/mopidy/mpd/dispatcher.py index c3881f6f..4ddb4025 100644 --- a/mopidy/mpd/dispatcher.py +++ b/mopidy/mpd/dispatcher.py @@ -7,7 +7,7 @@ import pykka from mopidy.mpd import exceptions, protocol -logger = logging.getLogger('mopidy.mpd.dispatcher') +logger = logging.getLogger(__name__) protocol.load_protocol_modules() diff --git a/mopidy/mpd/session.py b/mopidy/mpd/session.py index 5a531a79..2c0bd840 100644 --- a/mopidy/mpd/session.py +++ b/mopidy/mpd/session.py @@ -5,7 +5,7 @@ import logging from mopidy.mpd import dispatcher, protocol from mopidy.utils import formatting, network -logger = logging.getLogger('mopidy.mpd') +logger = logging.getLogger(__name__) class MpdSession(network.LineProtocol): diff --git a/mopidy/utils/network.py b/mopidy/utils/network.py index 1ffb12d6..bb1edbc4 100644 --- a/mopidy/utils/network.py +++ b/mopidy/utils/network.py @@ -12,7 +12,7 @@ import pykka from mopidy.utils import encoding -logger = logging.getLogger('mopidy.utils.server') +logger = logging.getLogger(__name__) class ShouldRetrySocketCall(Exception): diff --git a/mopidy/utils/path.py b/mopidy/utils/path.py index b8dcc589..29e8077e 100644 --- a/mopidy/utils/path.py +++ b/mopidy/utils/path.py @@ -9,7 +9,7 @@ import urlparse import glib -logger = logging.getLogger('mopidy.utils.path') +logger = logging.getLogger(__name__) XDG_DIRS = { diff --git a/mopidy/utils/process.py b/mopidy/utils/process.py index c8e3e558..0660efe0 100644 --- a/mopidy/utils/process.py +++ b/mopidy/utils/process.py @@ -8,7 +8,7 @@ import threading from pykka import ActorDeadError from pykka.registry import ActorRegistry -logger = logging.getLogger('mopidy.utils.process') +logger = logging.getLogger(__name__) SIGNALS = dict( (k, v) for v, k in signal.__dict__.iteritems() diff --git a/mopidy/zeroconf.py b/mopidy/zeroconf.py index 671bebc7..e95b1792 100644 --- a/mopidy/zeroconf.py +++ b/mopidy/zeroconf.py @@ -4,7 +4,7 @@ import logging import socket import string -logger = logging.getLogger('mopidy.zeroconf') +logger = logging.getLogger(__name__) try: import dbus From 30a648ee75bcef63d719e636709bdef80510c424 Mon Sep 17 00:00:00 2001 From: Colin Date: Thu, 2 Jan 2014 15:45:18 +0000 Subject: [PATCH 11/24] Fixed typo --- docs/glossary.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/glossary.rst b/docs/glossary.rst index 2acb9981..19c799d4 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -16,7 +16,7 @@ Glossary :term:`tracklist`. To use the core module, see :ref:`core-api`. extension - A Python package that can extend Mopidy with on or more + A Python package that can extend Mopidy with one or more :term:`backends `, :term:`frontends `, or GStreamer elements like :term:`mixers `. See :ref:`ext` for a list of existing extensions and :ref:`extensiondev` for how to make a new From 7c8a63e7be97b015d9aa59c78e70ac5b081a8bae Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Thu, 2 Jan 2014 23:21:54 +0100 Subject: [PATCH 12/24] docs: Update authors --- .mailmap | 3 +++ AUTHORS | 2 ++ 2 files changed, 5 insertions(+) diff --git a/.mailmap b/.mailmap index 2cc42b4c..a427c69c 100644 --- a/.mailmap +++ b/.mailmap @@ -11,3 +11,6 @@ Alexandre Petitjean Javier Domingo Cansino Lasse Bigum Nick Steel +Janez Troha +Luke Giuliani +Colin Montgomerie diff --git a/AUTHORS b/AUTHORS index d3e86ef1..cba5edc2 100644 --- a/AUTHORS +++ b/AUTHORS @@ -30,3 +30,5 @@ - Lasse Bigum - David Eisner - Pål Ruud +- Luke Giuliani +- Colin Montgomerie From 32252cb10053c4ac31e6089b0116c29cfe9a6584 Mon Sep 17 00:00:00 2001 From: Paul Connolley Date: Fri, 3 Jan 2014 11:26:13 +0000 Subject: [PATCH 13/24] Updated npm version to 0.2.0 After using this in our live mopidy system for the last 2 weeks, I am quite happy about the stability of the release. I've updated the package.json file as it makes sense to incorporate this in to my final commit for issue #609 --- js/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/package.json b/js/package.json index 6d6c8a89..b278c08b 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "mopidy", - "version": "0.1.1", + "version": "0.2.0", "description": "Client lib for controlling a Mopidy music server over a WebSocket", "homepage": "http://www.mopidy.com/", "author": { From d8ba9d8ecb19677d84e92b6f3e86d79fd65b2051 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sun, 15 Dec 2013 21:38:48 +0100 Subject: [PATCH 14/24] docs: Unbreak docs building (cherry picked from commit 7b667028e140ee56f6a365c8135f812eaaaaa284) --- docs/config.rst | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/config.rst b/docs/config.rst index c15ad1cc..bad49373 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -29,9 +29,8 @@ config value, you **should not** add it to :file:`~/.config/mopidy/mopidy.conf`. To see what's the effective configuration for your Mopidy installation, you can -run :option:`mopidy config`. It will print your full effective config -with passwords masked out so that you safely can share the output with others -for debugging. +run ``mopidy config``. It will print your full effective config with passwords +masked out so that you safely can share the output with others for debugging. You can find a description of all config values belonging to Mopidy's core below, together with their default values. In addition, all :ref:`extensions From 67fafd67d22612f2c630a8813282d13ea46d456d Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 3 Jan 2014 23:30:15 +0100 Subject: [PATCH 15/24] docs: Add hint about installing extension packages (fixes #627) --- docs/installation/index.rst | 13 +++++++++++++ docs/installation/raspberrypi.rst | 13 +++++++++++++ 2 files changed, 26 insertions(+) diff --git a/docs/installation/index.rst b/docs/installation/index.rst index 456ae73a..fb3de75b 100644 --- a/docs/installation/index.rst +++ b/docs/installation/index.rst @@ -42,6 +42,19 @@ in the same way as you get updates to the rest of your distribution. sudo apt-get update sudo apt-get install mopidy + Note that this will only install the main Mopidy package. For e.g. Spotify + or SoundCloud support you need to install the respective extension packages. + To list all the extensions available from apt.mopidy.com, you can run:: + + apt-cache search mopidy + + To install one of the listed packages, e.g. ``mopidy-spotify``, simply run:: + + sudo apt-get install mopidy-spotify + + For a full list of available Mopidy extensions, including those not + installable from apt.mopidy.com, see :ref:`ext`. + #. Finally, you need to set a couple of :doc:`config values `, and then you're ready to :doc:`run Mopidy `. diff --git a/docs/installation/raspberrypi.rst b/docs/installation/raspberrypi.rst index 4bc17a26..4eb25072 100644 --- a/docs/installation/raspberrypi.rst +++ b/docs/installation/raspberrypi.rst @@ -62,6 +62,19 @@ you a lot better performance. sudo apt-get update sudo apt-get install mopidy + Note that this will only install the main Mopidy package. For e.g. Spotify + or SoundCloud support you need to install the respective extension packages. + To list all the extensions available from apt.mopidy.com, you can run:: + + apt-cache search mopidy + + To install one of the listed packages, e.g. ``mopidy-spotify``, simply run:: + + sudo apt-get install mopidy-spotify + + For a full list of available Mopidy extensions, including those not + installable from apt.mopidy.com, see :ref:`ext`. + #. Since I have a HDMI cable connected, but want the sound on the analog sound connector, I have to run:: From ac7ff2744ddb982c72562d6260d67b140c9cb95d Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 3 Jan 2014 23:59:39 +0100 Subject: [PATCH 16/24] js: Update Node instructions, add changelog --- js/README.md | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/js/README.md b/js/README.md index 753e858a..8c582feb 100644 --- a/js/README.md +++ b/js/README.md @@ -35,7 +35,7 @@ Mopidy.js using npm: After npm completes, you can import Mopidy.js using ``require()``: - var Mopidy = require("mopidy").Mopidy; + var Mopidy = require("mopidy"); Using the library @@ -80,3 +80,26 @@ To run other [grunt](http://gruntjs.com/) targets which isn't predefined in `package.json` and thus isn't available through `npm run-script`: PATH=./node_modules/.bin:$PATH grunt foo + + +Changelog +--------- + +### 0.2.0 (UNRELEASED) + +- **Backwards incompatible change for Node.js users:** + `var Mopidy = require('mopidy').Mopidy;` must be changed to + `var Mopidy = require('mopidy');` + +- Add support for [Browserify](http://browserify.org/). + +- Upgrade dependencies. + +### 0.1.1 (2013-09-17) + +- Upgrade dependencies. + +### 0.1.0 (2013-03-31) + +- Initial release as a Node.js module to the + [npm registry](https://npmjs.org/). From c7e96cf992b8fee7be8eb2249f64f190b10a2ec6 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sat, 4 Jan 2014 00:05:31 +0100 Subject: [PATCH 17/24] js: Update all dependencies --- js/package.json | 18 +++++++++--------- js/test/mopidy-test.js | 5 ++++- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/js/package.json b/js/package.json index b278c08b..d16cfaa9 100644 --- a/js/package.json +++ b/js/package.json @@ -14,19 +14,19 @@ }, "main": "src/mopidy.js", "dependencies": { - "bane": "~1.0.0", - "faye-websocket": "~0.7.0", - "when": "~2.7.0" + "bane": "~1.1.0", + "faye-websocket": "~0.7.2", + "when": "~2.7.1" }, "devDependencies": { - "buster": "~0.6.13", - "grunt": "~0.4.1", - "grunt-buster": "~0.2.1", + "buster": "~0.7.8", + "grunt": "~0.4.2", + "grunt-buster": "~0.3.1", "grunt-browserify": "~1.3.0", - "grunt-contrib-jshint": "~0.6.4", - "grunt-contrib-uglify": "~0.2.4", + "grunt-contrib-jshint": "~0.8.0", + "grunt-contrib-uglify": "~0.2.7", "grunt-contrib-watch": "~0.5.3", - "phantomjs": "~1.9.2-0" + "phantomjs": "~1.9.2-6" }, "scripts": { "test": "grunt test", diff --git a/js/test/mopidy-test.js b/js/test/mopidy-test.js index ee34d845..9f2509fc 100644 --- a/js/test/mopidy-test.js +++ b/js/test/mopidy-test.js @@ -1,4 +1,4 @@ -/*global require:false, assert:false, refute:false*/ +/*global require:false */ if (typeof module === "object" && typeof require === "function") { var buster = require("buster"); @@ -6,6 +6,9 @@ if (typeof module === "object" && typeof require === "function") { var when = require("when"); } +var assert = buster.assert; +var refute = buster.refute; + buster.testCase("Mopidy", { setUp: function () { // Sinon.JS doesn't manage to stub PhantomJS' WebSocket implementation, From edc27135fd4d56bcdb09ed248486ba35beb783cf Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sat, 4 Jan 2014 00:07:12 +0100 Subject: [PATCH 18/24] js: Build updated mopidy.js --- mopidy/http/data/mopidy.js | 422 ++++++++++++++++----------------- mopidy/http/data/mopidy.min.js | 6 +- 2 files changed, 207 insertions(+), 221 deletions(-) diff --git a/mopidy/http/data/mopidy.js b/mopidy/http/data/mopidy.js index 857d826b..cc72e3e6 100644 --- a/mopidy/http/data/mopidy.js +++ b/mopidy/http/data/mopidy.js @@ -1,8 +1,8 @@ -/*! Mopidy.js - built 2013-12-15 +/*! Mopidy.js - built 2014-01-04 * http://www.mopidy.com/ - * Copyright (c) 2013 Stein Magnus Jodal and contributors + * Copyright (c) 2014 Stein Magnus Jodal and contributors * Licensed under the Apache License, Version 2.0 */ -!function(e){"object"==typeof exports?module.exports=e():"function"==typeof define&&define.amd?define(e):"undefined"!=typeof window?window.Mopidy=e():"undefined"!=typeof global?global.Mopidy=e():"undefined"!=typeof self&&(self.Mopidy=e())}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 0) { var fn = queue.shift(); @@ -236,7 +248,7 @@ var process=require("__browserify_process");/** @license MIT License (c) copyrig * * @author Brian Cavalier * @author John Hann - * @version 2.7.0 + * @version 2.7.1 */ (function(define) { 'use strict'; define(function (require) { @@ -282,8 +294,14 @@ define(function (require) { return cast(promiseOrValue).then(onFulfilled, onRejected, onProgress); } - function cast(x) { - return x instanceof Promise ? x : resolve(x); + /** + * Creates a new promise whose fate is determined by resolver. + * @param {function} resolver function(resolve, reject, notify) + * @returns {Promise} promise whose fate is determine by resolver + */ + function promise(resolver) { + return new Promise(resolver, + monitorApi.PromiseStatus && monitorApi.PromiseStatus()); } /** @@ -291,16 +309,90 @@ define(function (require) { * a trusted when.js promise. Any other duck-typed promise is considered * untrusted. * @constructor - * @param {function} sendMessage function to deliver messages to the promise's handler - * @param {function?} inspect function that reports the promise's state + * @returns {Promise} promise whose fate is determine by resolver * @name Promise */ - function Promise(sendMessage, inspect) { - this._message = sendMessage; + function Promise(resolver, status) { + var self, value, consumers = []; + + self = this; + this._status = status; this.inspect = inspect; + this._when = _when; + + // Call the provider resolver to seal the promise's fate + try { + resolver(promiseResolve, promiseReject, promiseNotify); + } catch(e) { + promiseReject(e); + } + + /** + * Returns a snapshot of this promise's current status at the instant of call + * @returns {{state:String}} + */ + function inspect() { + return value ? value.inspect() : toPendingState(); + } + + /** + * Private message delivery. Queues and delivers messages to + * the promise's ultimate fulfillment value or rejection reason. + * @private + */ + function _when(resolve, notify, onFulfilled, onRejected, onProgress) { + consumers ? consumers.push(deliver) : enqueue(function() { deliver(value); }); + + function deliver(p) { + p._when(resolve, notify, onFulfilled, onRejected, onProgress); + } + } + + /** + * Transition from pre-resolution state to post-resolution state, notifying + * all listeners of the ultimate fulfillment or rejection + * @param {*} val resolution value + */ + function promiseResolve(val) { + if(!consumers) { + return; + } + + var queue = consumers; + consumers = undef; + + enqueue(function () { + value = coerce(self, val); + if(status) { + updateStatus(value, status); + } + runHandlers(queue, value); + }); + } + + /** + * Reject this promise with the supplied reason, which will be used verbatim. + * @param {*} reason reason for the rejection + */ + function promiseReject(reason) { + promiseResolve(new RejectedPromise(reason)); + } + + /** + * Issue a progress event, notifying all progress listeners + * @param {*} update progress event payload to pass to all listeners + */ + function promiseNotify(update) { + if(consumers) { + var queue = consumers; + enqueue(function () { + runHandlers(queue, new ProgressingPromise(update)); + }); + } + } } - var promisePrototype = Promise.prototype; + promisePrototype = Promise.prototype; /** * Register handlers for this promise. @@ -310,14 +402,10 @@ define(function (require) { * @return {Promise} new Promise */ promisePrototype.then = function(onFulfilled, onRejected, onProgress) { - /*jshint unused:false*/ - var args, sendMessage; + var self = this; - args = arguments; - sendMessage = this._message; - - return _promise(function(resolve, reject, notify) { - sendMessage('when', args, resolve, notify); + return new Promise(function(resolve, reject, notify) { + self._when(resolve, notify, onFulfilled, onRejected, onProgress); }, this._status && this._status.observed()); }; @@ -361,7 +449,7 @@ define(function (require) { * @returns {undefined} */ promisePrototype.done = function(handleResult, handleError) { - this.then(handleResult, handleError).otherwise(crash); + this.then(handleResult, handleError)['catch'](crash); }; /** @@ -412,12 +500,23 @@ define(function (require) { return this.then(onFulfilledOrRejected, onFulfilledOrRejected, onProgress); }; + /** + * Casts x to a trusted promise. If x is already a trusted promise, it is + * returned, otherwise a new trusted Promise which follows x is returned. + * @param {*} x + * @returns {Promise} + */ + function cast(x) { + return x instanceof Promise ? x : resolve(x); + } + /** * Returns a resolved promise. The returned promise will be * - fulfilled with promiseOrValue if it is a value, or * - if promiseOrValue is a promise * - fulfilled with promiseOrValue's value after it is fulfilled * - rejected with promiseOrValue's reason after it is rejected + * In contract to cast(x), this always creates a new Promise * @param {*} value * @return {Promise} */ @@ -438,7 +537,9 @@ define(function (require) { * @return {Promise} rejected {@link Promise} */ function reject(promiseOrValue) { - return when(promiseOrValue, rejected); + return when(promiseOrValue, function(e) { + return new RejectedPromise(e); + }); } /** @@ -483,7 +584,7 @@ define(function (require) { deferred.reject = deferred.resolver.reject = function(reason) { if(resolved) { - return resolve(rejected(reason)); + return resolve(new RejectedPromise(reason)); } resolved = true; rejectPending(reason); @@ -497,112 +598,6 @@ define(function (require) { } } - /** - * Creates a new promise whose fate is determined by resolver. - * @param {function} resolver function(resolve, reject, notify) - * @returns {Promise} promise whose fate is determine by resolver - */ - function promise(resolver) { - return _promise(resolver, monitorApi.PromiseStatus && monitorApi.PromiseStatus()); - } - - /** - * Creates a new promise, linked to parent, whose fate is determined - * by resolver. - * @param {function} resolver function(resolve, reject, notify) - * @param {Promise?} status promise from which the new promise is begotten - * @returns {Promise} promise whose fate is determine by resolver - * @private - */ - function _promise(resolver, status) { - var self, value, consumers = []; - - self = new Promise(_message, inspect); - self._status = status; - - // Call the provider resolver to seal the promise's fate - try { - resolver(promiseResolve, promiseReject, promiseNotify); - } catch(e) { - promiseReject(e); - } - - // Return the promise - return self; - - /** - * Private message delivery. Queues and delivers messages to - * the promise's ultimate fulfillment value or rejection reason. - * @private - * @param {String} type - * @param {Array} args - * @param {Function} resolve - * @param {Function} notify - */ - function _message(type, args, resolve, notify) { - consumers ? consumers.push(deliver) : enqueue(function() { deliver(value); }); - - function deliver(p) { - p._message(type, args, resolve, notify); - } - } - - /** - * Returns a snapshot of the promise's state at the instant inspect() - * is called. The returned object is not live and will not update as - * the promise's state changes. - * @returns {{ state:String, value?:*, reason?:* }} status snapshot - * of the promise. - */ - function inspect() { - return value ? value.inspect() : toPendingState(); - } - - /** - * Transition from pre-resolution state to post-resolution state, notifying - * all listeners of the ultimate fulfillment or rejection - * @param {*|Promise} val resolution value - */ - function promiseResolve(val) { - if(!consumers) { - return; - } - - var queue = consumers; - consumers = undef; - - enqueue(function () { - value = coerce(self, val); - if(status) { - updateStatus(value, status); - } - runHandlers(queue, value); - }); - - } - - /** - * Reject this promise with the supplied reason, which will be used verbatim. - * @param {*} reason reason for the rejection - */ - function promiseReject(reason) { - promiseResolve(rejected(reason)); - } - - /** - * Issue a progress event, notifying all progress listeners - * @param {*} update progress event payload to pass to all listeners - */ - function promiseNotify(update) { - if(consumers) { - var queue = consumers; - enqueue(function () { - runHandlers(queue, progressed(update)); - }); - } - } - } - /** * Run a queue of functions as quickly as possible, passing * value to each. @@ -613,67 +608,6 @@ define(function (require) { } } - /** - * Creates a fulfilled, local promise as a proxy for a value - * NOTE: must never be exposed - * @param {*} value fulfillment value - * @returns {Promise} - */ - function fulfilled(value) { - return near( - new NearFulfilledProxy(value), - function() { return toFulfilledState(value); } - ); - } - - /** - * Creates a rejected, local promise with the supplied reason - * NOTE: must never be exposed - * @param {*} reason rejection reason - * @returns {Promise} - */ - function rejected(reason) { - return near( - new NearRejectedProxy(reason), - function() { return toRejectedState(reason); } - ); - } - - /** - * Creates a near promise using the provided proxy - * NOTE: must never be exposed - * @param {object} proxy proxy for the promise's ultimate value or reason - * @param {function} inspect function that returns a snapshot of the - * returned near promise's state - * @returns {Promise} - */ - function near(proxy, inspect) { - return new Promise(function (type, args, resolve) { - try { - resolve(proxy[type].apply(proxy, args)); - } catch(e) { - resolve(rejected(e)); - } - }, inspect); - } - - /** - * Create a progress promise with the supplied update. - * @private - * @param {*} update - * @return {Promise} progress promise - */ - function progressed(update) { - return new Promise(function (type, args, _, notify) { - var onProgress = args[2]; - try { - notify(typeof onProgress === 'function' ? onProgress(update) : update); - } catch(e) { - notify(e); - } - }); - } - /** * Coerces x to a trusted Promise * @param {*} x thing to coerce @@ -685,7 +619,7 @@ define(function (require) { */ function coerce(self, x) { if (x === self) { - return rejected(new TypeError()); + return new RejectedPromise(new TypeError()); } if (x instanceof Promise) { @@ -697,9 +631,9 @@ define(function (require) { return typeof untrustedThen === 'function' ? assimilate(untrustedThen, x) - : fulfilled(x); + : new FulfilledPromise(x); } catch(e) { - return rejected(e); + return new RejectedPromise(e); } } @@ -715,36 +649,89 @@ define(function (require) { }); } + makePromisePrototype = Object.create || + function(o) { + function PromisePrototype() {} + PromisePrototype.prototype = o; + return new PromisePrototype(); + }; + /** - * Proxy for a near, fulfilled value - * @param {*} value - * @constructor + * Creates a fulfilled, local promise as a proxy for a value + * NOTE: must never be exposed + * @private + * @param {*} value fulfillment value + * @returns {Promise} */ - function NearFulfilledProxy(value) { + function FulfilledPromise(value) { this.value = value; } - NearFulfilledProxy.prototype.when = function(onResult) { - return typeof onResult === 'function' ? onResult(this.value) : this.value; + FulfilledPromise.prototype = makePromisePrototype(promisePrototype); + + FulfilledPromise.prototype.inspect = function() { + return toFulfilledState(this.value); }; - /** - * Proxy for a near rejection - * @param {*} reason - * @constructor - */ - function NearRejectedProxy(reason) { - this.reason = reason; - } - - NearRejectedProxy.prototype.when = function(_, onError) { - if(typeof onError === 'function') { - return onError(this.reason); - } else { - throw this.reason; + FulfilledPromise.prototype._when = function(resolve, _, onFulfilled) { + try { + resolve(typeof onFulfilled === 'function' ? onFulfilled(this.value) : this.value); + } catch(e) { + resolve(new RejectedPromise(e)); } }; + /** + * Creates a rejected, local promise as a proxy for a value + * NOTE: must never be exposed + * @private + * @param {*} reason rejection reason + * @returns {Promise} + */ + function RejectedPromise(reason) { + this.value = reason; + } + + RejectedPromise.prototype = makePromisePrototype(promisePrototype); + + RejectedPromise.prototype.inspect = function() { + return toRejectedState(this.value); + }; + + RejectedPromise.prototype._when = function(resolve, _, __, onRejected) { + try { + resolve(typeof onRejected === 'function' ? onRejected(this.value) : this); + } catch(e) { + resolve(new RejectedPromise(e)); + } + }; + + /** + * Create a progress promise with the supplied update. + * @private + * @param {*} value progress update value + * @return {Promise} progress promise + */ + function ProgressingPromise(value) { + this.value = value; + } + + ProgressingPromise.prototype = makePromisePrototype(promisePrototype); + + ProgressingPromise.prototype._when = function(_, notify, f, r, u) { + try { + notify(typeof u === 'function' ? u(this.value) : this.value); + } catch(e) { + notify(e); + } + }; + + /** + * Update a PromiseStatus monitor object with the outcome + * of the supplied value promise. + * @param {Promise} value + * @param {PromiseStatus} status + */ function updateStatus(value, status) { value.then(statusFulfilled, statusRejected); @@ -921,7 +908,7 @@ define(function (require) { function _map(array, mapFunc, fallback) { return when(array, function(array) { - return _promise(resolveMap); + return new Promise(resolveMap); function resolveMap(resolve, reject, notify) { var results, len, toResolve, i; @@ -1028,7 +1015,7 @@ define(function (require) { // Internals, utilities, etc. // - var reduceArray, slice, fcall, nextTick, handlerQueue, + var promisePrototype, makePromisePrototype, reduceArray, slice, fcall, nextTick, handlerQueue, funcProto, call, arrayProto, monitorApi, capturedSetTimeout, cjsRequire, MutationObs, undef; @@ -1475,5 +1462,4 @@ module.exports = Mopidy; },{"../lib/websocket/":1,"bane":2,"when":4}]},{},[5]) (5) -}); -; \ No newline at end of file +}); \ No newline at end of file diff --git a/mopidy/http/data/mopidy.min.js b/mopidy/http/data/mopidy.min.js index 5e61a3f6..450911bd 100644 --- a/mopidy/http/data/mopidy.min.js +++ b/mopidy/http/data/mopidy.min.js @@ -1,5 +1,5 @@ -/*! Mopidy.js - built 2013-12-15 +/*! Mopidy.js - built 2014-01-04 * http://www.mopidy.com/ - * Copyright (c) 2013 Stein Magnus Jodal and contributors + * Copyright (c) 2014 Stein Magnus Jodal and contributors * Licensed under the Apache License, Version 2.0 */ -!function(a){"object"==typeof exports?module.exports=a():"function"==typeof define&&define.amd?define(a):"undefined"!=typeof window?window.Mopidy=a():"undefined"!=typeof global?global.Mopidy=a():"undefined"!=typeof self&&(self.Mopidy=a())}(function(){var a;return function b(a,c,d){function e(g,h){if(!c[g]){if(!a[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);throw new Error("Cannot find module '"+g+"'")}var j=c[g]={exports:{}};a[g][0].call(j.exports,function(b){var c=a[g][1][b];return e(c?c:b)},j,j.exports,b,a,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;g0)for(d=0;e>d;++d)c[d](a,b);else setTimeout(function(){throw b.message=a+" listener threw error: "+b.message,b},0)}function b(a){if("function"!=typeof a)throw new TypeError("Listener is not function");return a}function c(a){return a.supervisors||(a.supervisors=[]),a.supervisors}function d(a,b){return a.listeners||(a.listeners={}),b&&!a.listeners[b]&&(a.listeners[b]=[]),b?a.listeners[b]:a.listeners}function e(a){return a.errbacks||(a.errbacks=[]),a.errbacks}function f(f){function h(b,c,d){try{c.listener.apply(c.thisp||f,d)}catch(g){a(b,g,e(f))}}return f=f||{},f.on=function(a,e,f){return"function"==typeof a?c(this).push({listener:a,thisp:e}):(d(this,a).push({listener:b(e),thisp:f}),void 0)},f.off=function(a,b){var f,g,h,i;if(!a){f=c(this),f.splice(0,f.length),g=d(this);for(h in g)g.hasOwnProperty(h)&&(f=d(this,h),f.splice(0,f.length));return f=e(this),f.splice(0,f.length),void 0}if("function"==typeof a?(f=c(this),b=a):f=d(this,a),!b)return f.splice(0,f.length),void 0;for(h=0,i=f.length;i>h;++h)if(f[h].listener===b)return f.splice(h,1),void 0},f.once=function(a,b,c){var d=function(){f.off(a,d),b.apply(this,arguments)};f.on(a,d,c)},f.bind=function(a,b){var c,d,e;if(b)for(d=0,e=b.length;e>d;++d){if("function"!=typeof a[b[d]])throw new Error("No such method "+b[d]);this.on(b[d],a[b[d]],a)}else for(c in a)"function"==typeof a[c]&&this.on(c,a[c],a);return a},f.emit=function(a){var b,e,f=c(this),i=g.call(arguments);for(b=0,e=f.length;e>b;++b)h(a,f[b],i);for(f=d(this,a).slice(),i=g.call(arguments,1),b=0,e=f.length;e>b;++b)h(a,f[b],i)},f.errback=function(a){this.errbacks||(this.errbacks=[]),this.errbacks.push(b(a))},f}var g=Array.prototype.slice;return{createEventEmitter:f}})},{}],3:[function(a,b){var c=b.exports={};c.nextTick=function(){var a="undefined"!=typeof window&&window.setImmediate,b="undefined"!=typeof window&&window.postMessage&&window.addEventListener;if(a)return function(a){return window.setImmediate(a)};if(b){var c=[];return window.addEventListener("message",function(a){if(a.source===window&&"process-tick"===a.data&&(a.stopPropagation(),c.length>0)){var b=c.shift();b()}},!0),function(a){c.push(a),window.postMessage("process-tick","*")}}return function(a){setTimeout(a,0)}}(),c.title="browser",c.browser=!0,c.env={},c.argv=[],c.binding=function(){throw new Error("process.binding is not supported")},c.cwd=function(){return"/"},c.chdir=function(){throw new Error("process.chdir is not supported")}},{}],4:[function(b,c){var d=b("__browserify_process");!function(a){"use strict";a(function(a){function b(a,b,d,e){return c(a).then(b,d,e)}function c(a){return a instanceof e?a:f(a)}function e(a,b){this._message=a,this.inspect=b}function f(a){return i(function(b){b(a)})}function g(a){return b(a,m)}function h(){function a(a,e,g){b.resolve=b.resolver.resolve=function(b){return d?f(b):(d=!0,a(b),c)},b.reject=b.resolver.reject=function(a){return d?f(m(a)):(d=!0,e(a),c)},b.notify=b.resolver.notify=function(a){return g(a),a}}var b,c,d;return b={promise:X,resolve:X,reject:X,notify:X,resolver:{resolve:X,reject:X,notify:X}},b.promise=c=i(a),b}function i(a){return j(a,T.PromiseStatus&&T.PromiseStatus())}function j(a,b){function c(a,b,c,d){function e(e){e._message(a,b,c,d)}l?l.push(e):G(function(){e(j)})}function d(){return j?j.inspect():F()}function f(a){if(l){var c=l;l=X,G(function(){j=p(i,a),b&&t(j,b),k(c,j)})}}function g(a){f(m(a))}function h(a){if(l){var b=l;G(function(){k(b,o(a))})}}var i,j,l=[];i=new e(c,d),i._status=b;try{a(f,g,h)}catch(n){g(n)}return i}function k(a,b){for(var c=0;c>>0,i=Math.max(0,Math.min(c,o)),k=[],j=o-i+1,l=[],i)for(n=function(a){l.push(a),--j||(m=n=I,e(l))},m=function(a){k.push(a),--i||(m=n=I,d(k))},p=0;o>p;++p)p in a&&b(a[p],h,g,f);else d(k)}return i(g).then(d,e,f)})}function w(a,b,c,d){function e(a){return b?b(a[0]):a[0]}return v(a,1,e,c,d)}function x(a,b,c,d){return B(a,I).then(b,c,d)}function y(){return B(arguments,I)}function z(a){return B(a,D,E)}function A(a,b){return B(a,b)}function B(a,c,d){return b(a,function(a){function e(e,f,g){function h(a,h){b(a,c,d).then(function(a){i[h]=a,--k||e(i)},f,g)}var i,j,k,l;if(k=j=a.length>>>0,i=[],!k)return e(i),void 0;for(l=0;j>l;l++)l in a?h(a[l],l):--k}return j(e)})}function C(a,c){var d=N(M,arguments,1);return b(a,function(a){var e;return e=a.length,d[0]=function(a,d,f){return b(a,function(a){return b(d,function(b){return c(a,b,f,e)})})},L.apply(a,d)})}function D(a){return{state:"fulfilled",value:a}}function E(a){return{state:"rejected",reason:a}}function F(){return{state:"pending"}}function G(a){1===P.push(a)&&O(H)}function H(){k(P),P=[]}function I(a){return a}function J(a){throw"function"==typeof T.reportUnhandled?T.reportUnhandled():G(function(){throw a}),a}b.promise=i,b.resolve=f,b.reject=g,b.defer=h,b.join=y,b.all=x,b.map=A,b.reduce=C,b.settle=z,b.any=w,b.some=v,b.isPromise=u,b.isPromiseLike=u;var K=e.prototype;K.then=function(){var a,b;return a=arguments,b=this._message,j(function(c,d,e){b("when",a,c,e)},this._status&&this._status.observed())},K["catch"]=K.otherwise=function(a){return this.then(X,a)},K["finally"]=K.ensure=function(a){function b(){return f(a())}return"function"==typeof a?this.then(b,b).yield(this):this},K.done=function(a,b){this.then(a,b).otherwise(J)},K.yield=function(a){return this.then(function(){return a})},K.tap=function(a){return this.then(a).yield(this)},K.spread=function(a){return this.then(function(b){return x(b,function(b){return a.apply(X,b)})})},K.always=function(a,b){return this.then(a,a,b)},r.prototype.when=function(a){return"function"==typeof a?a(this.value):this.value},s.prototype.when=function(a,b){if("function"==typeof b)return b(this.reason);throw this.reason};var L,M,N,O,P,Q,R,S,T,U,V,W,X;if(V=a,P=[],T="undefined"!=typeof console?console:b,"object"==typeof d&&d.nextTick)O=d.nextTick;else if(W="function"==typeof MutationObserver&&MutationObserver||"function"==typeof WebKitMutationObserver&&WebKitMutationObserver)O=function(a,b,c){var d=a.createElement("div");return new b(c).observe(d,{attributes:!0}),function(){d.setAttribute("x","x")}}(document,W,H);else try{O=V("vertx").runOnLoop||V("vertx").runOnContext}catch(Y){U=setTimeout,O=function(a){U(a,0)}}return Q=Function.prototype,R=Q.call,N=Q.bind?R.bind(R):function(a,b){return a.apply(b,M.call(arguments,2))},S=[],M=S.slice,L=S.reduce||function(a){var b,c,d,e,f;if(f=0,b=Object(this),e=b.length>>>0,c=arguments,c.length<=1)for(;;){if(f in b){d=b[f++];break}if(++f>=e)throw new TypeError}else d=c[1];for(;e>f;++f)f in b&&(d=a(d,b[f],f,b));return d},b})}("function"==typeof a&&a.amd?a:function(a){c.exports=a(b)})},{__browserify_process:3}],5:[function(a,b){function c(a){return this instanceof c?(this._settings=this._configure(a||{}),this._console=this._getConsole(),this._backoffDelay=this._settings.backoffDelayMin,this._pendingRequests={},this._webSocket=null,d.createEventEmitter(this),this._delegateEvents(),this._settings.autoConnect&&this.connect(),void 0):new c(a)}var d=a("bane"),e=a("../lib/websocket/"),f=a("when");c.WebSocket=e.Client,c.prototype._configure=function(a){var b="undefined"!=typeof document&&document.location.host||"localhost";return a.webSocketUrl=a.webSocketUrl||"ws://"+b+"/mopidy/ws/",a.autoConnect!==!1&&(a.autoConnect=!0),a.backoffDelayMin=a.backoffDelayMin||1e3,a.backoffDelayMax=a.backoffDelayMax||64e3,a},c.prototype._getConsole=function(){var a="undefined"!=typeof a&&a||{};return a.log=a.log||function(){},a.warn=a.warn||function(){},a.error=a.error||function(){},a},c.prototype._delegateEvents=function(){this.off("websocket:close"),this.off("websocket:error"),this.off("websocket:incomingMessage"),this.off("websocket:open"),this.off("state:offline"),this.on("websocket:close",this._cleanup),this.on("websocket:error",this._handleWebSocketError),this.on("websocket:incomingMessage",this._handleMessage),this.on("websocket:open",this._resetBackoffDelay),this.on("websocket:open",this._getApiSpec),this.on("state:offline",this._reconnect)},c.prototype.connect=function(){if(this._webSocket){if(this._webSocket.readyState===c.WebSocket.OPEN)return;this._webSocket.close()}this._webSocket=this._settings.webSocket||new c.WebSocket(this._settings.webSocketUrl),this._webSocket.onclose=function(a){this.emit("websocket:close",a)}.bind(this),this._webSocket.onerror=function(a){this.emit("websocket:error",a)}.bind(this),this._webSocket.onopen=function(){this.emit("websocket:open")}.bind(this),this._webSocket.onmessage=function(a){this.emit("websocket:incomingMessage",a)}.bind(this)},c.prototype._cleanup=function(a){Object.keys(this._pendingRequests).forEach(function(b){var c=this._pendingRequests[b];delete this._pendingRequests[b],c.reject({message:"WebSocket closed",closeEvent:a})}.bind(this)),this.emit("state:offline")},c.prototype._reconnect=function(){this.emit("reconnectionPending",{timeToAttempt:this._backoffDelay}),setTimeout(function(){this.emit("reconnecting"),this.connect()}.bind(this),this._backoffDelay),this._backoffDelay=2*this._backoffDelay,this._backoffDelay>this._settings.backoffDelayMax&&(this._backoffDelay=this._settings.backoffDelayMax)},c.prototype._resetBackoffDelay=function(){this._backoffDelay=this._settings.backoffDelayMin},c.prototype.close=function(){this.off("state:offline",this._reconnect),this._webSocket.close()},c.prototype._handleWebSocketError=function(a){this._console.warn("WebSocket error:",a.stack||a)},c.prototype._send=function(a){var b=f.defer();switch(this._webSocket.readyState){case c.WebSocket.CONNECTING:b.resolver.reject({message:"WebSocket is still connecting"});break;case c.WebSocket.CLOSING:b.resolver.reject({message:"WebSocket is closing"});break;case c.WebSocket.CLOSED:b.resolver.reject({message:"WebSocket is closed"});break;default:a.jsonrpc="2.0",a.id=this._nextRequestId(),this._pendingRequests[a.id]=b.resolver,this._webSocket.send(JSON.stringify(a)),this.emit("websocket:outgoingMessage",a)}return b.promise},c.prototype._nextRequestId=function(){var a=-1;return function(){return a+=1}}(),c.prototype._handleMessage=function(a){try{var b=JSON.parse(a.data);b.hasOwnProperty("id")?this._handleResponse(b):b.hasOwnProperty("event")?this._handleEvent(b):this._console.warn("Unknown message type received. Message was: "+a.data)}catch(c){if(!(c instanceof SyntaxError))throw c;this._console.warn("WebSocket message parsing failed. Message was: "+a.data)}},c.prototype._handleResponse=function(a){if(!this._pendingRequests.hasOwnProperty(a.id))return this._console.warn("Unexpected response received. Message was:",a),void 0;var b=this._pendingRequests[a.id];delete this._pendingRequests[a.id],a.hasOwnProperty("result")?b.resolve(a.result):a.hasOwnProperty("error")?(b.reject(a.error),this._console.warn("Server returned error:",a.error)):(b.reject({message:"Response without 'result' or 'error' received",data:{response:a}}),this._console.warn("Response without 'result' or 'error' received. Message was:",a))},c.prototype._handleEvent=function(a){var b=a.event,c=a;delete c.event,this.emit("event:"+this._snakeToCamel(b),c)},c.prototype._getApiSpec=function(){return this._send({method:"core.describe"}).then(this._createApi.bind(this),this._handleWebSocketError).then(null,this._handleWebSocketError)},c.prototype._createApi=function(a){var b=function(a){return function(){var b=Array.prototype.slice.call(arguments);return this._send({method:a,params:b})}.bind(this)}.bind(this),c=function(a){var b=a.split(".");return b.length>=1&&"core"===b[0]&&(b=b.slice(1)),b},d=function(a){var b=this;return a.forEach(function(a){a=this._snakeToCamel(a),b[a]=b[a]||{},b=b[a]}.bind(this)),b}.bind(this),e=function(e){var f=c(e),g=this._snakeToCamel(f.slice(-1)[0]),h=d(f.slice(0,-1));h[g]=b(e),h[g].description=a[e].description,h[g].params=a[e].params}.bind(this);Object.keys(a).forEach(e),this.emit("state:online")},c.prototype._snakeToCamel=function(a){return a.replace(/(_[a-z])/g,function(a){return a.toUpperCase().replace("_","")})},b.exports=c},{"../lib/websocket/":1,bane:2,when:4}]},{},[5])(5)}); \ No newline at end of file +!function(a){if("object"==typeof exports)module.exports=a();else if("function"==typeof define&&define.amd)define(a);else{var b;"undefined"!=typeof window?b=window:"undefined"!=typeof global?b=global:"undefined"!=typeof self&&(b=self),b.Mopidy=a()}}(function(){var a;return function b(a,c,d){function e(g,h){if(!c[g]){if(!a[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);throw new Error("Cannot find module '"+g+"'")}var j=c[g]={exports:{}};a[g][0].call(j.exports,function(b){var c=a[g][1][b];return e(c?c:b)},j,j.exports,b,a,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;g0)for(d=0;e>d;++d)c[d](a,b);else setTimeout(function(){throw b.message=a+" listener threw error: "+b.message,b},0)}function b(a){if("function"!=typeof a)throw new TypeError("Listener is not function");return a}function c(a){return a.supervisors||(a.supervisors=[]),a.supervisors}function d(a,b){return a.listeners||(a.listeners={}),b&&!a.listeners[b]&&(a.listeners[b]=[]),b?a.listeners[b]:a.listeners}function e(a){return a.errbacks||(a.errbacks=[]),a.errbacks}function f(f){function h(b,c,d){try{c.listener.apply(c.thisp||f,d)}catch(g){a(b,g,e(f))}}return f=f||{},f.on=function(a,e,f){return"function"==typeof a?c(this).push({listener:a,thisp:e}):(d(this,a).push({listener:b(e),thisp:f}),void 0)},f.off=function(a,b){var f,g,h,i;if(!a){f=c(this),f.splice(0,f.length),g=d(this);for(h in g)g.hasOwnProperty(h)&&(f=d(this,h),f.splice(0,f.length));return f=e(this),f.splice(0,f.length),void 0}if("function"==typeof a?(f=c(this),b=a):f=d(this,a),!b)return f.splice(0,f.length),void 0;for(h=0,i=f.length;i>h;++h)if(f[h].listener===b)return f.splice(h,1),void 0},f.once=function(a,b,c){var d=function(){f.off(a,d),b.apply(this,arguments)};f.on(a,d,c)},f.bind=function(a,b){var c,d,e;if(b)for(d=0,e=b.length;e>d;++d){if("function"!=typeof a[b[d]])throw new Error("No such method "+b[d]);this.on(b[d],a[b[d]],a)}else for(c in a)"function"==typeof a[c]&&this.on(c,a[c],a);return a},f.emit=function(a){var b,e,f=c(this),i=g.call(arguments);for(b=0,e=f.length;e>b;++b)h(a,f[b],i);for(f=d(this,a).slice(),i=g.call(arguments,1),b=0,e=f.length;e>b;++b)h(a,f[b],i)},f.errback=function(a){this.errbacks||(this.errbacks=[]),this.errbacks.push(b(a))},f}var g=Array.prototype.slice;return{createEventEmitter:f,aggregate:function(a){var b=f();return a.forEach(function(a){a.on(function(a,c){b.emit(a,c)})}),b}}})},{}],3:[function(a,b){var c=b.exports={};c.nextTick=function(){var a="undefined"!=typeof window&&window.setImmediate,b="undefined"!=typeof window&&window.postMessage&&window.addEventListener;if(a)return function(a){return window.setImmediate(a)};if(b){var c=[];return window.addEventListener("message",function(a){var b=a.source;if((b===window||null===b)&&"process-tick"===a.data&&(a.stopPropagation(),c.length>0)){var d=c.shift();d()}},!0),function(a){c.push(a),window.postMessage("process-tick","*")}}return function(a){setTimeout(a,0)}}(),c.title="browser",c.browser=!0,c.env={},c.argv=[],c.binding=function(){throw new Error("process.binding is not supported")},c.cwd=function(){return"/"},c.chdir=function(){throw new Error("process.chdir is not supported")}},{}],4:[function(b,c){var d=b("__browserify_process");!function(a){"use strict";a(function(a){function b(a,b,c,d){return f(a).then(b,c,d)}function c(a){return new e(a,Q.PromiseStatus&&Q.PromiseStatus())}function e(a,b){function c(){return i?i.inspect():B()}function d(a,b,c,d,e){function f(f){f._when(a,b,c,d,e)}l?l.push(f):C(function(){f(i)})}function e(a){if(l){var c=l;l=U,C(function(){i=k(h,a),b&&p(i,b),j(c,i)})}}function f(a){e(new n(a))}function g(a){if(l){var b=l;C(function(){j(b,new o(a))})}}var h,i,l=[];h=this,this._status=b,this.inspect=c,this._when=d;try{a(e,f,g)}catch(m){f(m)}}function f(a){return a instanceof e?a:g(a)}function g(a){return c(function(b){b(a)})}function h(a){return b(a,function(a){return new n(a)})}function i(){function a(a,c,f){b.resolve=b.resolver.resolve=function(b){return e?g(b):(e=!0,a(b),d)},b.reject=b.resolver.reject=function(a){return e?g(new n(a)):(e=!0,c(a),d)},b.notify=b.resolver.notify=function(a){return f(a),a}}var b,d,e;return b={promise:U,resolve:U,reject:U,notify:U,resolver:{resolve:U,reject:U,notify:U}},b.promise=d=c(a),b}function j(a,b){for(var c=0;c>>0,i=Math.max(0,Math.min(d,o)),k=[],j=o-i+1,l=[],i)for(n=function(a){l.push(a),--j||(m=n=E,e(l))},m=function(a){k.push(a),--i||(m=n=E,c(k))},p=0;o>p;++p)p in a&&b(a[p],h,g,f);else c(k)}return c(h).then(e,f,g)})}function s(a,b,c,d){function e(a){return b?b(a[0]):a[0]}return r(a,1,e,c,d)}function t(a,b,c,d){return x(a,E).then(b,c,d)}function u(){return x(arguments,E)}function v(a){return x(a,z,A)}function w(a,b){return x(a,b)}function x(a,c,d){return b(a,function(a){function f(e,f,g){function h(a,h){b(a,c,d).then(function(a){i[h]=a,--k||e(i)},f,g)}var i,j,k,l;if(k=j=a.length>>>0,i=[],!k)return e(i),void 0;for(l=0;j>l;l++)l in a?h(a[l],l):--k}return new e(f)})}function y(a,c){var d=K(J,arguments,1);return b(a,function(a){var e;return e=a.length,d[0]=function(a,d,f){return b(a,function(a){return b(d,function(b){return c(a,b,f,e)})})},I.apply(a,d)})}function z(a){return{state:"fulfilled",value:a}}function A(a){return{state:"rejected",reason:a}}function B(){return{state:"pending"}}function C(a){1===M.push(a)&&L(D)}function D(){j(M),M=[]}function E(a){return a}function F(a){throw"function"==typeof Q.reportUnhandled?Q.reportUnhandled():C(function(){throw a}),a}b.promise=c,b.resolve=g,b.reject=h,b.defer=i,b.join=u,b.all=t,b.map=w,b.reduce=y,b.settle=v,b.any=s,b.some=r,b.isPromise=q,b.isPromiseLike=q,G=e.prototype,G.then=function(a,b,c){var d=this;return new e(function(e,f,g){d._when(e,g,a,b,c)},this._status&&this._status.observed())},G["catch"]=G.otherwise=function(a){return this.then(U,a)},G["finally"]=G.ensure=function(a){function b(){return g(a())}return"function"==typeof a?this.then(b,b).yield(this):this},G.done=function(a,b){this.then(a,b)["catch"](F)},G.yield=function(a){return this.then(function(){return a})},G.tap=function(a){return this.then(a).yield(this)},G.spread=function(a){return this.then(function(b){return t(b,function(b){return a.apply(U,b)})})},G.always=function(a,b){return this.then(a,a,b)},H=Object.create||function(a){function b(){}return b.prototype=a,new b},m.prototype=H(G),m.prototype.inspect=function(){return z(this.value)},m.prototype._when=function(a,b,c){try{a("function"==typeof c?c(this.value):this.value)}catch(d){a(new n(d))}},n.prototype=H(G),n.prototype.inspect=function(){return A(this.value)},n.prototype._when=function(a,b,c,d){try{a("function"==typeof d?d(this.value):this)}catch(e){a(new n(e))}},o.prototype=H(G),o.prototype._when=function(a,b,c,d,e){try{b("function"==typeof e?e(this.value):this.value)}catch(f){b(f)}};var G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U;if(S=a,M=[],Q="undefined"!=typeof console?console:b,"object"==typeof d&&d.nextTick)L=d.nextTick;else if(T="function"==typeof MutationObserver&&MutationObserver||"function"==typeof WebKitMutationObserver&&WebKitMutationObserver)L=function(a,b,c){var d=a.createElement("div");return new b(c).observe(d,{attributes:!0}),function(){d.setAttribute("x","x")}}(document,T,D);else try{L=S("vertx").runOnLoop||S("vertx").runOnContext}catch(V){R=setTimeout,L=function(a){R(a,0)}}return N=Function.prototype,O=N.call,K=N.bind?O.bind(O):function(a,b){return a.apply(b,J.call(arguments,2))},P=[],J=P.slice,I=P.reduce||function(a){var b,c,d,e,f;if(f=0,b=Object(this),e=b.length>>>0,c=arguments,c.length<=1)for(;;){if(f in b){d=b[f++];break}if(++f>=e)throw new TypeError}else d=c[1];for(;e>f;++f)f in b&&(d=a(d,b[f],f,b));return d},b})}("function"==typeof a&&a.amd?a:function(a){c.exports=a(b)})},{__browserify_process:3}],5:[function(a,b){function c(a){return this instanceof c?(this._settings=this._configure(a||{}),this._console=this._getConsole(),this._backoffDelay=this._settings.backoffDelayMin,this._pendingRequests={},this._webSocket=null,d.createEventEmitter(this),this._delegateEvents(),this._settings.autoConnect&&this.connect(),void 0):new c(a)}var d=a("bane"),e=a("../lib/websocket/"),f=a("when");c.WebSocket=e.Client,c.prototype._configure=function(a){var b="undefined"!=typeof document&&document.location.host||"localhost";return a.webSocketUrl=a.webSocketUrl||"ws://"+b+"/mopidy/ws/",a.autoConnect!==!1&&(a.autoConnect=!0),a.backoffDelayMin=a.backoffDelayMin||1e3,a.backoffDelayMax=a.backoffDelayMax||64e3,a},c.prototype._getConsole=function(){var a="undefined"!=typeof a&&a||{};return a.log=a.log||function(){},a.warn=a.warn||function(){},a.error=a.error||function(){},a},c.prototype._delegateEvents=function(){this.off("websocket:close"),this.off("websocket:error"),this.off("websocket:incomingMessage"),this.off("websocket:open"),this.off("state:offline"),this.on("websocket:close",this._cleanup),this.on("websocket:error",this._handleWebSocketError),this.on("websocket:incomingMessage",this._handleMessage),this.on("websocket:open",this._resetBackoffDelay),this.on("websocket:open",this._getApiSpec),this.on("state:offline",this._reconnect)},c.prototype.connect=function(){if(this._webSocket){if(this._webSocket.readyState===c.WebSocket.OPEN)return;this._webSocket.close()}this._webSocket=this._settings.webSocket||new c.WebSocket(this._settings.webSocketUrl),this._webSocket.onclose=function(a){this.emit("websocket:close",a)}.bind(this),this._webSocket.onerror=function(a){this.emit("websocket:error",a)}.bind(this),this._webSocket.onopen=function(){this.emit("websocket:open")}.bind(this),this._webSocket.onmessage=function(a){this.emit("websocket:incomingMessage",a)}.bind(this)},c.prototype._cleanup=function(a){Object.keys(this._pendingRequests).forEach(function(b){var c=this._pendingRequests[b];delete this._pendingRequests[b],c.reject({message:"WebSocket closed",closeEvent:a})}.bind(this)),this.emit("state:offline")},c.prototype._reconnect=function(){this.emit("reconnectionPending",{timeToAttempt:this._backoffDelay}),setTimeout(function(){this.emit("reconnecting"),this.connect()}.bind(this),this._backoffDelay),this._backoffDelay=2*this._backoffDelay,this._backoffDelay>this._settings.backoffDelayMax&&(this._backoffDelay=this._settings.backoffDelayMax)},c.prototype._resetBackoffDelay=function(){this._backoffDelay=this._settings.backoffDelayMin},c.prototype.close=function(){this.off("state:offline",this._reconnect),this._webSocket.close()},c.prototype._handleWebSocketError=function(a){this._console.warn("WebSocket error:",a.stack||a)},c.prototype._send=function(a){var b=f.defer();switch(this._webSocket.readyState){case c.WebSocket.CONNECTING:b.resolver.reject({message:"WebSocket is still connecting"});break;case c.WebSocket.CLOSING:b.resolver.reject({message:"WebSocket is closing"});break;case c.WebSocket.CLOSED:b.resolver.reject({message:"WebSocket is closed"});break;default:a.jsonrpc="2.0",a.id=this._nextRequestId(),this._pendingRequests[a.id]=b.resolver,this._webSocket.send(JSON.stringify(a)),this.emit("websocket:outgoingMessage",a)}return b.promise},c.prototype._nextRequestId=function(){var a=-1;return function(){return a+=1}}(),c.prototype._handleMessage=function(a){try{var b=JSON.parse(a.data);b.hasOwnProperty("id")?this._handleResponse(b):b.hasOwnProperty("event")?this._handleEvent(b):this._console.warn("Unknown message type received. Message was: "+a.data)}catch(c){if(!(c instanceof SyntaxError))throw c;this._console.warn("WebSocket message parsing failed. Message was: "+a.data)}},c.prototype._handleResponse=function(a){if(!this._pendingRequests.hasOwnProperty(a.id))return this._console.warn("Unexpected response received. Message was:",a),void 0;var b=this._pendingRequests[a.id];delete this._pendingRequests[a.id],a.hasOwnProperty("result")?b.resolve(a.result):a.hasOwnProperty("error")?(b.reject(a.error),this._console.warn("Server returned error:",a.error)):(b.reject({message:"Response without 'result' or 'error' received",data:{response:a}}),this._console.warn("Response without 'result' or 'error' received. Message was:",a))},c.prototype._handleEvent=function(a){var b=a.event,c=a;delete c.event,this.emit("event:"+this._snakeToCamel(b),c)},c.prototype._getApiSpec=function(){return this._send({method:"core.describe"}).then(this._createApi.bind(this),this._handleWebSocketError).then(null,this._handleWebSocketError)},c.prototype._createApi=function(a){var b=function(a){return function(){var b=Array.prototype.slice.call(arguments);return this._send({method:a,params:b})}.bind(this)}.bind(this),c=function(a){var b=a.split(".");return b.length>=1&&"core"===b[0]&&(b=b.slice(1)),b},d=function(a){var b=this;return a.forEach(function(a){a=this._snakeToCamel(a),b[a]=b[a]||{},b=b[a]}.bind(this)),b}.bind(this),e=function(e){var f=c(e),g=this._snakeToCamel(f.slice(-1)[0]),h=d(f.slice(0,-1));h[g]=b(e),h[g].description=a[e].description,h[g].params=a[e].params}.bind(this);Object.keys(a).forEach(e),this.emit("state:online")},c.prototype._snakeToCamel=function(a){return a.replace(/(_[a-z])/g,function(a){return a.toUpperCase().replace("_","")})},b.exports=c},{"../lib/websocket/":1,bane:2,when:4}]},{},[5])(5)}); \ No newline at end of file From aa8406e3097ec0b5b9cb7ca60db6361c0f3654d1 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sat, 4 Jan 2014 00:10:48 +0100 Subject: [PATCH 19/24] js: Remove unused when-define-shim.js --- js/lib/when-define-shim.js | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 js/lib/when-define-shim.js diff --git a/js/lib/when-define-shim.js b/js/lib/when-define-shim.js deleted file mode 100644 index ad135517..00000000 --- a/js/lib/when-define-shim.js +++ /dev/null @@ -1,11 +0,0 @@ -if (typeof window !== "undefined") { - window.define = function (factory) { - try { - delete window.define; - } catch (e) { - window.define = void 0; // IE - } - window.when = factory(); - }; - window.define.amd = {}; -} From a7d38df853af7980d45981dc1533c36682a1eb84 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sat, 4 Jan 2014 00:13:19 +0100 Subject: [PATCH 20/24] js: Release Mopidy.js 0.2.0 to npm --- js/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/README.md b/js/README.md index 8c582feb..5a04cd66 100644 --- a/js/README.md +++ b/js/README.md @@ -85,7 +85,7 @@ To run other [grunt](http://gruntjs.com/) targets which isn't predefined in Changelog --------- -### 0.2.0 (UNRELEASED) +### 0.2.0 (2014-01-04) - **Backwards incompatible change for Node.js users:** `var Mopidy = require('mopidy').Mopidy;` must be changed to From 7f467802f3b6aba39a056d68386e6dbd7304185a Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sat, 4 Jan 2014 00:14:36 +0100 Subject: [PATCH 21/24] docs: Update changelog --- docs/changelog.rst | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 3898e5f3..b0e9fa45 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -37,6 +37,12 @@ a temporary regression of :issue:`527`. - Live lookup of URI metadata has been added. (Fixes :issue:`540`) +**HTTP frontend** + +- Upgrade Mopidy.js dependencies and add support for using Mopidy.js with + Browserify. This version has been released to npm as Mopidy.js v0.2.0. + (Fixes: :issue:`609`) + **Internal changes** - Events from the audio actor, backends, and core actor are now emitted @@ -414,7 +420,7 @@ A release with a number of small and medium fixes, with no specific focus. objects with ``tlid`` set to ``0`` to be sent to the HTTP client without the ``tlid`` field. (Fixes: :issue:`501`) -- Upgrade Mopidy.js dependencies. This version has been released to NPM as +- Upgrade Mopidy.js dependencies. This version has been released to npm as Mopidy.js v0.1.1. **Extension support** From 789610c85ab325761f77afedbb23340c8d417777 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sat, 4 Jan 2014 00:39:57 +0100 Subject: [PATCH 22/24] docs: Update authors --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index cba5edc2..c048b83e 100644 --- a/AUTHORS +++ b/AUTHORS @@ -30,5 +30,6 @@ - Lasse Bigum - David Eisner - Pål Ruud +- Paul Connolley - Luke Giuliani - Colin Montgomerie From 3f665ec29ac33a07cb01e913bc223321816c6443 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sat, 4 Jan 2014 14:10:19 +0100 Subject: [PATCH 23/24] Include all files not ignored by Git in PyPI releases --- MANIFEST.in | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/MANIFEST.in b/MANIFEST.in index b3a70f17..51ba5919 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,12 +1,23 @@ +include *.py include *.rst +include .coveragerc +include .mailmap +include .travis.yml +include AUTHORS include LICENSE include MANIFEST.in -include data/mopidy.desktop + +recursive-include data * recursive-include docs * prune docs/_build +recursive-include js * +prune js/node_modules +prune js/test/lib + recursive-include mopidy *.conf recursive-include mopidy/http/data * + recursive-include tests *.py recursive-include tests/data * From 26b3d268f7735d9ae71c552c61bd2e1b889c1bdc Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sat, 4 Jan 2014 16:16:25 +0100 Subject: [PATCH 24/24] docs: Change how to require() Mopidy.js --- docs/api/http.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/http.rst b/docs/api/http.rst index c57597c7..5561955d 100644 --- a/docs/api/http.rst +++ b/docs/api/http.rst @@ -129,7 +129,7 @@ After npm completes, you can import Mopidy.js using ``require()``: .. code-block:: js - var Mopidy = require("mopidy").Mopidy; + var Mopidy = require("mopidy"); Getting the library for development on the library