images: update screenshot
[chrome-ext/music-player-client.git] / js / mpc.js
index dce9fe04895e5781dc36df02a9f69da9bbde066d..1e3390b27b7d21100dfe22d95837c9c508323efe 100644 (file)
--- a/js/mpc.js
+++ b/js/mpc.js
@@ -5,14 +5,20 @@ function Mpc(socket, cb_update_state, debug_enabled) {
        this._cb_update_state = cb_update_state;
        this._debug_enabled = debug_enabled;
        this._queue = ['init'];
+       this._recv_buffer = [];
+       this._recv_buffer_last = 0;
        this.state = {};
 }
 
-Mpc.prototype.log = function(msg, obj) {
-       if (this._debug_enabled)
+Mpc.prototype.log = function(lvl, msg, obj) {
+       if (this._debug_enabled & lvl)
                console.log('mpc: ' + msg, obj);
 }
 
+Mpc.prototype.err = function(msg, obj) {
+       console.error('mpc: ' + msg, obj);
+}
+
 Mpc.prototype.set_debug = function(val) {
        this._debug_enabled = val;
 }
@@ -21,33 +27,86 @@ Mpc.prototype.send = function(msg) {
        var _this = this;
        this._queue.push(msg);
        this._socket.send(msg, function(x) {
-               _this.log('send: ' + msg + ':', x);
+               _this.log(0x1, 'send: ' + msg + ':', x);
+               if (x.bytesWritten < 0) {
+                       _this.log(0x1, 'reconnecting...');
+                       _this._socket.reconnect();
+                       _this.queue = [msg];
+                       _this._socket.send(msg);
+               }
        });
 }
 
+Mpc.prototype._parse_result = function(lines) {
+       var state = {};
+       var keys = [];
+       var key, val, i;
+
+       lines.forEach(function(line) {
+               i = line.indexOf(':');
+               if (i == -1)
+                       return; // Ignores the OK line
+               key = line.substr(0, i);
+               keys.push(key);
+               val = line.substr(i + 2);
+               state[key] = val;
+       });
+
+       return {
+               'state': state,
+               'keys': keys,
+       };
+}
+
 Mpc.prototype.recv_msg = function(lines) {
-       curr = this._queue.shift();
-       this.log('recv: [' + curr + ']:', lines.join('\n'));
+       var state, keys, ret;
+       var curr = this._queue.shift();
+       this.log(0x2, 'recv: [' + curr + ']:', lines.join('\n'));
+       if (lines[0].substr(0, 4) == 'ACK ')
+               this.err(curr, lines.join('\n'));
        curr = curr.split(' ');
 
        switch (curr[0]) {
-       // Needs to return a list of dicts (see above for dicts).
-       //case 'playlistinfo':
+       case 'playlistinfo':
+               var i = 2, playlist = [], song;
+               while (i < lines.length) {
+                       if (lines[i].substr(0, 5) == 'file:') {
+                               song = this._parse_result(lines.splice(0, i + 1)).state;
+                               playlist = playlist.concat(song);
+                               i = 0;
+                       } else
+                               ++i;
+               }
+               this.state.Playlist = playlist;
+               this._cb_update_state(this.state);
+               break;
+
        case 'currentsong':
+               this.state.Currentsong = this._parse_result(lines).state;
+               this._cb_update_state(this.state);
+               break;
+
        case 'stats':
        case 'status':
-               state = {};
-               lines.forEach(function(line) {
-                       i = line.indexOf(':');
-                       if (i == -1)
-                               return; // Ignores the OK line
-                       key = line.substr(0, i);
-                       val = line.substr(i + 2);
-                       state[key] = val;
+               ret = this._parse_result(lines);
+               state = ret.state;
+               keys = ret.keys;
+
+               // When mpd is stopped, it gives us back crap values for some things.
+               if ('state' in state && state.state == 'stop') {
+                       if ('volume' in state && state.volume == '-1')
+                               keys.splice(keys.indexOf('volume'), 1);
+               }
+               // Now merge the current state with the previous one so that we don't
+               // lose information like volume or song position.
+               var curr_state = this.state;
+               keys.forEach(function(key) {
+                       curr_state[key] = state[key];
                });
-               this.state = state;
-               this._cb_update_state(state);
+
+               this._cb_update_state(curr_state);
                break;
+
        default:
                this._cb_update_state(lines, curr);
                break;
@@ -56,16 +115,23 @@ Mpc.prototype.recv_msg = function(lines) {
 
 Mpc.prototype.recv = function(msg) {
        /* We can get back a bunch of responses in a row, so parse them out */
-       /* XXX: Do we have to handle partial reads ?  like long playlists ... */
-       lines = msg.split('\n');
+       var lines = this._recv_buffer = this._recv_buffer.concat(msg.split('\n'));
        var i = 0;
        while (i < lines.length) {
-               if (lines[i] == 'OK' || lines[i].substr(0, 3) == 'OK ') {
+               if (lines[i] == 'OK' || lines[i].substr(0, 3) == 'OK ' ||
+                   lines[i].substr(0, 4) == 'ACK ') {
                        this.recv_msg(lines.splice(0, i + 1));
                        i = 0;
                } else
                        ++i;
        }
+
+       if (lines.length && this._recv_buffer_last != lines.length) {
+               // Keep sucking in data so long as more exists.
+               this._recv_buffer_last = lines.length;
+               this._socket.poll();
+       } else
+               this._recv_buffer_last = lines.length;
 }
 
 /*
@@ -79,7 +145,7 @@ Mpc.__make_send_void = function(cmd) {
 Mpc.__make_send_arg1 = function(cmd) {
        return function(a1) {
                if (a1 === undefined)
-                       this.log(cmd + ': function requires one argument');
+                       this.err(cmd + ': function requires one argument');
                else
                        this.send(cmd + ' ' + a1);
        }
@@ -88,12 +154,21 @@ Mpc.__make_send_arg1 = function(cmd) {
 Mpc.__make_send_arg2 = function(cmd) {
        return function(a1, a2) {
                if (a1 === undefined || a2 === undefined)
-                       this.log(cmd + ': function requires two arguments');
+                       this.err(cmd + ': function requires two arguments');
                else
                        this.send(cmd + ' ' + a1 + ' ' + a2);
        }
 }
 
+Mpc.__make_send_arg3 = function(cmd) {
+       return function(a1, a2, a3) {
+               if (a1 === undefined || a2 === undefined || a3 == undefined)
+                       this.err(cmd + ': function requires three arguments');
+               else
+                       this.send(cmd + ' ' + a1 + ' ' + a2 + ' ' + a3);
+       }
+}
+
 Mpc.__make_send_opt = function(cmd) {
        return function(arg) {
                if (arg === undefined)
@@ -109,7 +184,7 @@ Mpc.__make_send_range = function(cmd, min, max, def) {
                if (arg >= min && arg <= max)
                        this.send(cmd + ' ' + arg);
                else
-                       this.log(cmd + ': arg must be [' + min + ',' + max + '] but got "' + arg + '"');
+                       this.err(cmd + ': arg must be [' + min + ',' + max + '] but got "' + arg + '"');
        };
 }
 
@@ -175,10 +250,89 @@ Mpc.prototype.seek                = Mpc.__make_send_arg2('seek');
 // seekid {SONGID} {TIME}
 Mpc.prototype.seekid              = Mpc.__make_send_arg2('seekid');
 // seekcur {TIME}
-Mpc.prototype.seekcur             = Mpc.__make_send_arg1('seek');
+Mpc.prototype.seekcur             = Mpc.__make_send_arg1('seekcur');
 // stop
 Mpc.prototype.stop                = Mpc.__make_send_void('stop');
 
+/*
+ * The current playlist
+ * http://www.musicpd.org/doc/protocol/ch03s04.html
+ */
+
+// add {URI}
+Mpc.prototype.add                 = Mpc.__make_send_arg1('add');
+// addid {URI} [POSITION]
+// TODO: handle position
+Mpc.prototype.addid               = Mpc.__make_send_arg1('addid');
+// clear
+Mpc.prototype.clear               = Mpc.__make_send_void('clear');
+// delete [{POS} | {START:END}]
+Mpc.prototype.delete              = Mpc.__make_send_arg1('delete');
+// deleteid {SONGID}
+Mpc.prototype.deleteid            = Mpc.__make_send_arg1('deleteid');
+// move [{FROM} | {START:END}] {TO}
+Mpc.prototype.move                = Mpc.__make_send_arg2('move');
+// moveid {FROM} {TO}
+Mpc.prototype.moveid              = Mpc.__make_send_arg2('moveid');
+// playlist
+Mpc.prototype.playlist            = Mpc.__make_send_void('playlist');
+// playlistfind {TAG} {NEEDLE}
+Mpc.prototype.playlistfind        = Mpc.__make_send_arg2('playlistfind');
+// playlistid {SONGID}
+Mpc.prototype.playlistid          = Mpc.__make_send_arg1('playlistid');
+// playlistinfo [[SONGPOS] | [START:END]]
+Mpc.prototype.playlistinfo        = Mpc.__make_send_opt('playlistinfo');
+// playlistsearch {TAG} {NEEDLE}
+Mpc.prototype.playlistsearch      = Mpc.__make_send_arg2('playlistsearch');
+// plchanges {VERSION}
+Mpc.prototype.plchanges           = Mpc.__make_send_arg1('plchanges');
+// plchangesposid {VERSION}
+Mpc.prototype.plchangesposid      = Mpc.__make_send_arg1('plchangesposid');
+// prio {PRIORITY} {START:END...}
+Mpc.prototype.prio                = Mpc.__make_send_arg2('prio');
+// prioid {PRIORITY} {ID...}
+Mpc.prototype.prioid              = Mpc.__make_send_arg2('prioid');
+// shuffle [START:END]
+Mpc.prototype.shuffle             = Mpc.__make_send_opt('shuffle');
+// swap {SONG1} {SONG2}
+Mpc.prototype.swap                = Mpc.__make_send_arg2('swap');
+// swapid {SONG1} {SONG2}
+Mpc.prototype.swapid              = Mpc.__make_send_arg2('swapid');
+
+/*
+ * Stored playlists
+ * http://www.musicpd.org/doc/protocol/ch03s05.html
+ */
+
+// listplaylist {NAME}
+Mpc.prototype.listplaylist        = Mpc.__make_send_arg1('listplaylist');
+// listplaylistinfo {NAME}
+Mpc.prototype.listplaylistinfo    = Mpc.__make_send_arg1('listplaylistinfo');
+// listplaylists
+Mpc.prototype.listplaylists       = Mpc.__make_send_void('listplaylists');
+// load {NAME} [START:END]
+// TODO: handle optional start:end
+Mpc.prototype.load                = Mpc.__make_send_arg1('load');
+// playlistadd {NAME} {URI}
+Mpc.prototype.playlistadd         = Mpc.__make_send_arg2('playlistadd');
+// playlistclear {NAME}
+Mpc.prototype.playlistclear       = Mpc.__make_send_arg1('playlistclear');
+// playlistdelete {NAME} {SONGPOS}
+Mpc.prototype.playlistdelete      = Mpc.__make_send_arg2('playlistdelete');
+// playlistmove {NAME} {SONGID} {SONGPOS}
+Mpc.prototype.playlistmove        = Mpc.__make_send_arg3('playlistmove');
+// rename {NAME} {NEW_NAME}
+Mpc.prototype.rename              = Mpc.__make_send_arg2('rename');
+// rm {NAME}
+Mpc.prototype.rm                  = Mpc.__make_send_arg1('rm');
+// save {NAME}
+Mpc.prototype.save                = Mpc.__make_send_arg1('save');
+
+/*
+ * The music database
+ * http://www.musicpd.org/doc/protocol/ch03s06.html
+ */
+
 /*
  * Connection settings
  * http://www.musicpd.org/doc/protocol/ch03s08.html
@@ -192,3 +346,49 @@ Mpc.prototype.kill                = Mpc.__make_send_void('kill');
 Mpc.prototype.password            = Mpc.__make_send_arg1('password');
 // ping
 Mpc.prototype.ping                = Mpc.__make_send_void('ping');
+
+/*
+ * Audio output devices
+ * http://www.musicpd.org/doc/protocol/ch03s09.html
+ */
+
+// disableoutput {ID}
+Mpc.prototype.disableoutput       = Mpc.__make_send_arg1('disableoutput');
+// enableoutput {ID}
+Mpc.prototype.enableoutput        = Mpc.__make_send_arg1('enableoutput');
+// outputs
+Mpc.prototype.outputs             = Mpc.__make_send_void('outputs');
+
+/*
+ * Reflection
+ * http://www.musicpd.org/doc/protocol/ch03s10.html
+ */
+
+// config
+Mpc.prototype.config              = Mpc.__make_send_void('config');
+// commands
+Mpc.prototype.commands            = Mpc.__make_send_void('commands');
+// notcommands
+Mpc.prototype.notcommands         = Mpc.__make_send_void('notcommands');
+// tagtypes
+Mpc.prototype.tagtypes            = Mpc.__make_send_void('tagtypes');
+// urlhandlers
+Mpc.prototype.urlhandlers         = Mpc.__make_send_void('urlhandlers');
+// decoders
+Mpc.prototype.decoders            = Mpc.__make_send_void('decoders');
+
+/*
+ * Client to client
+ * http://www.musicpd.org/doc/protocol/ch03s11.html
+ */
+
+// subscribe {NAME}
+Mpc.prototype.subscribe           = Mpc.__make_send_arg1('subscribe');
+// unsubscribe {NAME}
+Mpc.prototype.unsubscribe         = Mpc.__make_send_arg1('unsubscribe');
+// channels
+Mpc.prototype.channels            = Mpc.__make_send_void('channels');
+// readmessages
+Mpc.prototype.readmessages        = Mpc.__make_send_void('readmessages');
+// sendmessage {CHANNEL} {TEXT}
+Mpc.prototype.sendmessage         = Mpc.__make_send_arg2('sendmessage');