MPD protocol: support async commands and implement add
This commit is contained in:
parent
4c78a11b64
commit
03404fa01f
1 changed files with 139 additions and 44 deletions
|
|
@ -1,4 +1,6 @@
|
|||
var net = require('net');
|
||||
var path = require('path');
|
||||
var findit = require('findit');
|
||||
var Player = require('../player');
|
||||
|
||||
module.exports = MpdServer;
|
||||
|
|
@ -128,6 +130,7 @@ MpdServer.prototype.initialize = function(cb) {
|
|||
return;
|
||||
}
|
||||
self.bootTime = new Date();
|
||||
self.singleMode = false;
|
||||
var server = net.createServer(onSocketConnection);
|
||||
server.listen(mpdPort, mpdHost, function() {
|
||||
console.info("MPD Protocol listening at " + mpdHost + ":" + mpdPort);
|
||||
|
|
@ -140,6 +143,8 @@ MpdServer.prototype.initialize = function(cb) {
|
|||
var cmdList = [];
|
||||
var okMode = false;
|
||||
var isIdle = false;
|
||||
var commandQueue = [];
|
||||
var ongoingCommand = false;
|
||||
var updatedSubsystems = {
|
||||
database: false,
|
||||
update: false,
|
||||
|
|
@ -253,86 +258,129 @@ MpdServer.prototype.initialize = function(cb) {
|
|||
if (state === STATE_ARG) {
|
||||
args.push(curArg);
|
||||
}
|
||||
handleCommand(cmd, args);
|
||||
commandQueue.push([cmd, args]);
|
||||
flushQueue();
|
||||
}
|
||||
|
||||
function handleCommand(cmdName, args) {
|
||||
function flushQueue() {
|
||||
if (ongoingCommand) return;
|
||||
var queueItem = commandQueue.shift();
|
||||
if (!queueItem) return;
|
||||
var cmd = queueItem[0];
|
||||
var args = queueItem[1];
|
||||
ongoingCommand = true;
|
||||
handleCommand(cmd, args, function() {
|
||||
ongoingCommand = false;
|
||||
flushQueue();
|
||||
});
|
||||
}
|
||||
|
||||
function handleCommand(cmdName, args, cb) {
|
||||
var cmdIndex = 0;
|
||||
|
||||
switch (cmdListState) {
|
||||
case CMD_LIST_STATE_NONE:
|
||||
if (cmdName === 'command_list_begin' && args.length === 0) {
|
||||
cmdListState = CMD_LIST_STATE_LIST;
|
||||
cmdList = [];
|
||||
okMode = false;
|
||||
cb();
|
||||
return;
|
||||
} else if (cmdName === 'command_list_ok_begin' && args.length === 0) {
|
||||
cmdListState = CMD_LIST_STATE_LIST;
|
||||
cmdList = [];
|
||||
okMode = true;
|
||||
cb();
|
||||
return;
|
||||
} else {
|
||||
if (!runOneCommand(cmdName, args, 0)) {
|
||||
socket.write("OK\n");
|
||||
}
|
||||
runOneCommand(cmdName, args, 0, function(ok) {
|
||||
if (ok) socket.write("OK\n");
|
||||
cb();
|
||||
});
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case CMD_LIST_STATE_LIST:
|
||||
if (cmdName === 'command_list_end' && args.length === 0) {
|
||||
var errorOccurred = false;
|
||||
for (var i = 0; i < cmdList.length; i += 1) {
|
||||
var commandPayload = cmdList[i];
|
||||
var thisCmdName = commandPayload[0];
|
||||
var thisCmdArgs = commandPayload[1];
|
||||
if (runOneCommand(thisCmdName, thisCmdArgs, i)) {
|
||||
errorOccurred = true;
|
||||
break;
|
||||
} else if (okMode) {
|
||||
socket.write("list_OK\n");
|
||||
}
|
||||
}
|
||||
if (!errorOccurred) {
|
||||
socket.write("OK\n");
|
||||
}
|
||||
cmdList = [];
|
||||
cmdListState = CMD_LIST_STATE_NONE;
|
||||
|
||||
runAndCheckOneCommand();
|
||||
return;
|
||||
|
||||
} else {
|
||||
cmdList.push([cmdName, args]);
|
||||
cb();
|
||||
return;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new Error("unrecognized state");
|
||||
}
|
||||
|
||||
function runAndCheckOneCommand() {
|
||||
var commandPayload = cmdList.shift();
|
||||
if (!commandPayload) {
|
||||
socket.write("OK\n");
|
||||
cb();
|
||||
return;
|
||||
}
|
||||
var thisCmdName = commandPayload[0];
|
||||
var thisCmdArgs = commandPayload[1];
|
||||
runOneCommand(thisCmdName, thisCmdArgs, cmdIndex++, function(ok) {
|
||||
if (!ok) {
|
||||
cb();
|
||||
return;
|
||||
} else if (okMode) {
|
||||
socket.write("list_OK\n");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function runOneCommand(cmdName, args, index) {
|
||||
function runOneCommand(cmdName, args, index, cb) {
|
||||
if (cmdName === 'noidle') {
|
||||
handleNoIdle(args);
|
||||
return true;
|
||||
cb(false);
|
||||
return;
|
||||
}
|
||||
if (isIdle) {
|
||||
socket.end();
|
||||
return true;
|
||||
cb(false);
|
||||
return;
|
||||
}
|
||||
if (cmdName === 'idle') {
|
||||
handleIdle(args);
|
||||
return true;
|
||||
cb(false);
|
||||
return;
|
||||
}
|
||||
var returnValue = execOneCommand(cmdName, args);
|
||||
if (returnValue) {
|
||||
var code = returnValue[0];
|
||||
var msg = returnValue[1];
|
||||
if (/unimplemented/.test(msg)) {
|
||||
console.info("needed command:", cmdName, JSON.stringify(args));
|
||||
execOneCommand(cmdName, args, cmdDone);
|
||||
|
||||
function cmdDone(code, msg) {
|
||||
if (code) {
|
||||
if (/unimplemented/.test(msg)) {
|
||||
console.info("needed command:", cmdName, JSON.stringify(args));
|
||||
}
|
||||
if (code === ERR_CODE_UNKNOWN) cmdName = "";
|
||||
socket.write("ACK [" + code + "@" + index + "] {" + cmdName + "} " + msg + "\n");
|
||||
cb(false);
|
||||
return;
|
||||
}
|
||||
if (code === ERR_CODE_UNKNOWN) cmdName = "";
|
||||
socket.write("ACK [" + code + "@" + index + "] {" + cmdName + "} " + msg + "\n");
|
||||
return true;
|
||||
cb(true);
|
||||
}
|
||||
}
|
||||
|
||||
function execOneCommand(cmdName, args) {
|
||||
if (!cmdName.length) return [ERR_CODE_UNKNOWN, "No command given"];
|
||||
function execOneCommand(cmdName, args, cb) {
|
||||
if (!cmdName.length) return cb(ERR_CODE_UNKNOWN, "No command given");
|
||||
var cmd = commands[cmdName];
|
||||
if (!cmd) return [ERR_CODE_UNKNOWN, "unknown command \"" + cmdName + "\""];
|
||||
return cmd(self, socket, args);
|
||||
if (!cmd) return cb(ERR_CODE_UNKNOWN, "unknown command \"" + cmdName + "\"");
|
||||
if (cmd.length < 4) {
|
||||
var returnValue = cmd(self, socket, args);
|
||||
var code = returnValue && returnValue[0];
|
||||
var msg = returnValue && returnValue[1];
|
||||
cb(code, msg);
|
||||
return;
|
||||
}
|
||||
cmd(self, socket, args, cb);
|
||||
}
|
||||
|
||||
function handleIdle(args) {
|
||||
|
|
@ -413,8 +461,37 @@ function writeTrackInfo(socket, dbTrack) {
|
|||
}
|
||||
}
|
||||
|
||||
function addCmd(self, socket, args) {
|
||||
return [ERR_CODE_UNKNOWN, "unimplemented"];
|
||||
function addCmd(self, socket, args, cb) {
|
||||
if (args.length !== 1) {
|
||||
return [ERR_CODE_ARG, "wrong number of arguments for \"add\""];
|
||||
}
|
||||
var uri = args[0];
|
||||
var musicDir = self.gb.config.musicDirectory;
|
||||
|
||||
var walker = findit(path.join(musicDir, uri));
|
||||
var files = [];
|
||||
walker.on('file', function(file) {
|
||||
console.info("found file", file);
|
||||
files.push(file);
|
||||
});
|
||||
walker.on('error', function(err) {
|
||||
walker.removeAllListeners();
|
||||
console.error("unable to walk file system:", err.stack);
|
||||
cb(ERR_CODE_UNKNOWN, "Unknown error");
|
||||
});
|
||||
walker.on('end', function() {
|
||||
var keys = [];
|
||||
for (var i = 0; i < files.length; i += 1) {
|
||||
var file = files[i];
|
||||
var relPath = path.relative(musicDir, file);
|
||||
var dbFile = self.gb.player.dbFilesByPath[relPath];
|
||||
console.info("found dbFile", dbFile);
|
||||
if (dbFile) keys.push(dbFile.key);
|
||||
}
|
||||
console.info("append keys", keys);
|
||||
self.gb.player.appendTracks(keys, false);
|
||||
cb();
|
||||
});
|
||||
}
|
||||
|
||||
function addidCmd(self, socket, args) {
|
||||
|
|
@ -803,15 +880,33 @@ function sendmessageCmd(self, socket, args) {
|
|||
}
|
||||
|
||||
function setvolCmd(self, socket, args) {
|
||||
return [ERR_CODE_UNKNOWN, "unimplemented"];
|
||||
if (args.length !== 1) {
|
||||
return [ERR_CODE_ARG, "wrong number of arguments for \"setvol\""];
|
||||
}
|
||||
var vol100 = parseFloat(args[0]);
|
||||
if (isNaN(vol100)) return [ERR_CODE_ARG, "Integer expected: " + args[0]];
|
||||
|
||||
self.gb.player.setVolume(vol100 / 100);
|
||||
}
|
||||
|
||||
function shuffleCmd(self, socket, args) {
|
||||
return [ERR_CODE_UNKNOWN, "unimplemented"];
|
||||
self.gb.player.shufflePlaylist();
|
||||
}
|
||||
|
||||
function singleCmd(self, socket, args) {
|
||||
return [ERR_CODE_UNKNOWN, "unimplemented"];
|
||||
switch (self.gb.player.repeat) {
|
||||
case Player.REPEAT_ONE:
|
||||
self.gb.player.setRepeat(Player.REPEAT_ALL);
|
||||
self.singleMode = false;
|
||||
break;
|
||||
case Player.REPEAT_ALL:
|
||||
self.gb.player.setRepeat(Player.REPEAT_ONE);
|
||||
self.singleMode = true;
|
||||
break;
|
||||
case Player.REPEAT_OFF:
|
||||
self.singleMode = !self.singleMode;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function statsCmd(self, socket, args) {
|
||||
|
|
@ -854,7 +949,7 @@ function statusCmd(self, socket, args) {
|
|||
break;
|
||||
case Player.REPEAT_OFF:
|
||||
repeat = 0;
|
||||
single = 0;
|
||||
single = +self.singleMode;
|
||||
break;
|
||||
}
|
||||
var playlistLength = self.gb.player.tracksInOrder.length;
|
||||
|
|
|
|||
Loading…
Reference in a new issue