mpd
Version:
music player daemon client
198 lines (171 loc) • 4.64 kB
JavaScript
var EventEmitter = require('events').EventEmitter
, util = require('util')
, assert = require('assert')
, net = require('net')
, MPD_SENTINEL = /^(OK|ACK|list_OK)(.*)$/m
, OK_MPD = /^OK MPD /
module.exports = MpdClient;
MpdClient.Command = Command
MpdClient.cmd = cmd;
MpdClient.parseKeyValueMessage = parseKeyValueMessage;
MpdClient.parseArrayMessage = parseArrayMessage;
function MpdClient() {
EventEmitter.call(this);
this.buffer = "";
this.msgHandlerQueue = [];
this.idling = false;
}
util.inherits(MpdClient, EventEmitter);
var defaultConnectOpts = {
host: 'localhost',
port: 6600
}
MpdClient.connect = function(options) {
options = options || defaultConnectOpts;
var client = new MpdClient();
client.socket = net.connect(options, function() {
client.emit('connect');
});
client.socket.setEncoding('utf8');
client.socket.on('data', function(data) {
client.receive(data);
});
client.socket.on('close', function() {
client.emit('end');
});
client.socket.on('error', function(err) {
client.emit('error', err);
});
return client;
}
MpdClient.prototype.receive = function(data) {
var m;
this.buffer += data;
while (m = this.buffer.match(MPD_SENTINEL)) {
var msg = this.buffer.substring(0, m.index)
, line = m[0]
, code = m[1]
, str = m[2]
if (code === "ACK") {
var err = new Error(str);
this.handleMessage(err);
} else if (OK_MPD.test(line)) {
this.setupIdling();
} else {
this.handleMessage(null, msg);
}
this.buffer = this.buffer.substring(msg.length + line.length + 1);
}
};
MpdClient.prototype.handleMessage = function(err, msg) {
var handler = this.msgHandlerQueue.shift();
handler(err, msg);
};
MpdClient.prototype.setupIdling = function() {
var self = this;
self.sendWithCallback("idle", function(err, msg) {
self.handleIdleResultsLoop(err, msg);
});
self.idling = true;
self.emit('ready');
};
MpdClient.prototype.sendCommand = function(command, callback) {
var self = this;
callback = callback || noop.bind(this);
assert.ok(self.idling);
self.send("noidle\n");
self.sendWithCallback(command, callback);
self.sendWithCallback("idle", function(err, msg) {
self.handleIdleResultsLoop(err, msg);
});
};
MpdClient.prototype.sendCommands = function(commandList, callback) {
var fullCmd = "command_list_begin\n" + commandList.join("\n") + "\ncommand_list_end";
this.sendCommand(fullCmd, callback || noop.bind(this));
};
MpdClient.prototype.handleIdleResultsLoop = function(err, msg) {
var self = this;
if (err) {
self.emit('error', err);
return;
}
self.handleIdleResults(msg);
if (self.msgHandlerQueue.length === 0) {
self.sendWithCallback("idle", function(err, msg) {
self.handleIdleResultsLoop(err, msg);
});
}
};
MpdClient.prototype.handleIdleResults = function(msg) {
var self = this;
msg.split("\n").forEach(function(system) {
if (system.length > 0) {
var name = system.substring(9);
self.emit('system-' + name);
self.emit('system', name);
}
});
};
MpdClient.prototype.sendWithCallback = function(cmd, cb) {
cb = cb || noop.bind(this);
this.msgHandlerQueue.push(cb);
this.send(cmd + "\n");
};
MpdClient.prototype.send = function(data) {
this.socket.write(data);
};
function Command(name, args) {
this.name = name;
this.args = args;
}
Command.prototype.toString = function() {
return this.name + " " + this.args.map(argEscape).join(" ");
};
function argEscape(arg){
// replace all " with \"
return '"' + arg.toString().replace(/"/g, '\\"') + '"';
}
function noop(err) {
if (err) this.emit('error', err);
}
// convenience
function cmd(name, args) {
return new Command(name, args);
}
function parseKeyValueMessage(msg) {
var result = {};
msg.split('\n').forEach(function(p){
if(p.length === 0) {
return;
}
var keyValue = p.match(/([^ ]+): (.*)/);
if (keyValue == null) {
throw new Error('Could not parse entry "' + p + '"')
}
result[keyValue[1]] = keyValue[2];
});
return result;
}
function parseArrayMessage(msg) {
var results = [];
var obj = {};
msg.split('\n').forEach(function(p) {
if(p.length === 0) {
return;
}
var keyValue = p.match(/([^ ]+): (.*)/);
if (keyValue == null) {
throw new Error('Could not parse entry "' + p + '"')
}
if (obj[keyValue[1]] !== undefined) {
results.push(obj);
obj = {};
obj[keyValue[1]] = keyValue[2];
}
else {
obj[keyValue[1]] = keyValue[2];
}
});
results.push(obj);
return results;
}