summaryrefslogtreecommitdiff
path: root/nephilim/mpd.py
diff options
context:
space:
mode:
Diffstat (limited to 'nephilim/mpd.py')
-rw-r--r--nephilim/mpd.py357
1 files changed, 0 insertions, 357 deletions
diff --git a/nephilim/mpd.py b/nephilim/mpd.py
deleted file mode 100644
index a9671c3..0000000
--- a/nephilim/mpd.py
+++ /dev/null
@@ -1,357 +0,0 @@
-# Python MPD client library
-# Copyright (C) 2008 J. Alexander Treuman <jat@spatialrift.net>
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-import socket
-import logging
-from PyQt4 import QtCore, QtNetwork
-
-
-HELLO_PREFIX = "OK MPD "
-ERROR_PREFIX = "ACK "
-SUCCESS = "OK"
-NEXT = "list_OK"
-
-
-class MPDError(Exception):
- pass
-
-class ConnectionError(MPDError):
- pass
-
-class ProtocolError(MPDError):
- pass
-
-class CommandError(MPDError):
- pass
-
-class CommandListError(MPDError):
- pass
-
-
-class _NotConnected(object):
- def __getattr__(self, attr):
- return self._dummy
-
- def _dummy(*args):
- raise ConnectionError("Not connected")
-
-class MPDClient(QtCore.QObject):
- # public
- logger = None
- mpd_version = None
-
- # private
- __sock = None
- _commandlist = None
-
- # SIGNALS
- connect_changed = QtCore.pyqtSignal(bool)
- def __init__(self):
- QtCore.QObject.__init__(self)
- self.logger = logging.getLogger('mpclient.mpdsocket')
- self._commands = {
- # Admin Commands
- "disableoutput": self._getnone,
- "enableoutput": self._getnone,
- "kill": None,
- "update": self._getitem,
- # Informational Commands
- "status": self._getobject,
- "stats": self._getobject,
- "outputs": self._getoutputs,
- "commands": self._getlist,
- "notcommands": self._getlist,
- "tagtypes": self._getlist,
- "urlhandlers": self._getlist,
- # Database Commands
- "find": self._getsongs,
- "findadd": self._getnone,
- "list": self._getlist,
- "listall": self._getdatabase,
- "listallinfo": self._getdatabase,
- "lsinfo": self._getdatabase,
- "search": self._getsongs,
- "count": self._getobject,
- # Playlist Commands
- "add": self._getnone,
- "addid": self._getitem,
- "clear": self._getnone,
- "currentsong": self._getobject,
- "delete": self._getnone,
- "deleteid": self._getnone,
- "load": self._getnone,
- "rename": self._getnone,
- "move": self._getnone,
- "moveid": self._getnone,
- "playlist": self._getplaylist,
- "playlistinfo": self._getsongs,
- "playlistid": self._getsongs,
- "plchanges": self._getsongs,
- "plchangesposid": self._getchanges,
- "rm": self._getnone,
- "save": self._getnone,
- "shuffle": self._getnone,
- "swap": self._getnone,
- "swapid": self._getnone,
- "listplaylist": self._getlist,
- "listplaylistinfo": self._getsongs,
- "playlistadd": self._getnone,
- "playlistclear": self._getnone,
- "playlistdelete": self._getnone,
- "playlistmove": self._getnone,
- "playlistfind": self._getsongs,
- "playlistsearch": self._getsongs,
- # Playback Commands
- "consume": self._getnone,
- "crossfade": self._getnone,
- "next": self._getnone,
- "pause": self._getnone,
- "play": self._getnone,
- "playid": self._getnone,
- "previous": self._getnone,
- "random": self._getnone,
- "repeat": self._getnone,
- "seek": self._getnone,
- "seekid": self._getnone,
- "setvol": self._getnone,
- "single": self._getnone,
- "stop": self._getnone,
- "volume": self._getnone,
- # Miscellaneous Commands
- "clearerror": self._getnone,
- "close": None,
- "password": self._getnone,
- "ping": self._getnone,
- }
-
- def __getattr__(self, attr):
- try:
- retval = self._commands[attr]
- except KeyError:
- raise AttributeError("'%s' object has no attribute '%s'" %
- (self.__class__.__name__, attr))
- return lambda *args: self._docommand(attr, args, retval)
-
- def _docommand(self, command, args, retval):
- if not self.__sock:
- self.logger.error('Cannot send command: not connected.')
- return None
- if self._commandlist is not None and not callable(retval):
- raise CommandListError("%s not allowed in command list" % command)
-
- self._writecommand(command, args)
-
- if self._commandlist is None:
- if callable(retval):
- return retval()
- return retval
- self._commandlist.append(retval)
-
- def _writecommand(self, command, args=[]):
- parts = [command]
- for arg in args:
- parts.append('"%s"' % escape(unicode(arg)))
- self.__sock.write(' '.join(parts).encode('utf-8') + '\n')
- self.__sock.waitForBytesWritten()
-
- def _readline(self):
- while not self.__sock.canReadLine():
- self.__sock.waitForReadyRead()
- line = str(self.__sock.readLine()).decode('utf-8')
- line = line.rstrip("\n")
- if line.startswith(ERROR_PREFIX):
- error = line[len(ERROR_PREFIX):].strip()
- raise CommandError(error)
- if self._commandlist is not None:
- if line == NEXT:
- return
- if line == SUCCESS:
- raise ProtocolError("Got unexpected '%s'" % SUCCESS)
- elif line == SUCCESS:
- return
- return line
-
- def _readitem(self, separator):
- line = self._readline()
- if line is None:
- return
- item = line.split(separator, 1)
- if len(item) < 2:
- raise ProtocolError("Could not parse item: '%s'" % line)
- return item
-
- def _readitems(self, separator=": "):
- item = self._readitem(separator)
- while item:
- yield item
- item = self._readitem(separator)
- raise StopIteration
-
- def _readlist(self):
- seen = None
- for key, value in self._readitems():
- if key != seen:
- if seen is not None:
- raise ProtocolError("Expected key '%s', got '%s'" %
- (seen, key))
- seen = key
- yield value
- raise StopIteration
-
- def _readplaylist(self):
- for key, value in self._readitems(":"):
- yield value
- raise StopIteration
-
- def _readobjects(self, delimiters=[]):
- obj = {}
- for key, value in self._readitems():
- key = key.lower()
- if obj:
- if key in delimiters:
- yield obj
- obj = {}
- elif obj.has_key(key):
- if not isinstance(obj[key], list):
- obj[key] = [obj[key], value]
- else:
- obj[key].append(value)
- continue
- obj[key] = value
- if obj:
- yield obj
- raise StopIteration
-
- def _readcommandlist(self):
- for retval in self._commandlist:
- yield retval()
- self._commandlist = None
- self._getnone()
- raise StopIteration
-
- def _getnone(self):
- line = self._readline()
- if line is not None:
- raise ProtocolError("Got unexpected return value: '%s'" % line)
-
- def _getitem(self):
- items = list(self._readitems())
- if len(items) != 1:
- return
- return items[0][1]
-
- def _getlist(self):
- return self._readlist()
-
- def _getplaylist(self):
- return self._readplaylist()
-
- def _getobject(self):
- objs = list(self._readobjects())
- if not objs:
- return {}
- return objs[0]
-
- def _getobjects(self, delimiters):
- return self._readobjects(delimiters)
-
- def _getsongs(self):
- return self._getobjects(["file"])
-
- def _getdatabase(self):
- return self._getobjects(["file", "directory", "playlist"])
-
- def _getoutputs(self):
- return self._getobjects(["outputid"])
-
- def _getchanges(self):
- return self._getobjects(["cpos"])
-
- def _getcommandlist(self):
- try:
- return self._readcommandlist()
- except CommandError:
- self._commandlist = None
- raise
-
- def __handle_error(self, error):
- self.logger.error(self.__sock.errorString())
- self.disconnect_mpd()
-
- def __finish_connect(self):
- # read MPD hello
- while not self.__sock.canReadLine():
- self.__sock.waitForReadyRead()
- line = str(self.__sock.readLine())
- if not line.startswith(HELLO_PREFIX):
- self.logger.error('Got invalid MPD hello: %s' % line)
- self.disconnect_mpd()
- return
- self.mpd_version = line[len(HELLO_PREFIX):].strip()
-
- self.connect_changed.emit(True)
-
- def connect_mpd(self, host, port):
- if self.__sock:
- return self.logger.error('Already connected.')
-
- if not port:
- #assume Unix domain socket
- self.__sock = QtNetwork.QLocalSocket(self)
- c = lambda host, port: self.__sock.connectToServer(host)
- else:
- self.__sock = QtNetwork.QTcpSocket(self)
- c = self.__sock.connectToHost
-
- self.__sock.error.connect( self.__handle_error)
- self.__sock.connected.connect(self.__finish_connect)
- c(host, port)
-
- def disconnect_mpd(self):
- if self.__sock:
- try:
- self.__sock.disconnectFromHost()
- except AttributeError:
- self.__sock.disconnectFromServer()
-
- if self.__sock.state() != QtNetwork.QAbstractSocket.UnconnectedState\
- and not self.__sock.waitForDisconnected(5000):
- self.__sock.abort()
-
- self.__sock = None
-
- self.mpd_version = None
- self._commandlist = None
- self.connect_changed.emit(False)
-
- def command_list_ok_begin(self):
- if self._commandlist is not None:
- raise CommandListError("Already in command list")
- self._writecommand("command_list_ok_begin")
- self._commandlist = []
-
- def command_list_end(self):
- if self._commandlist is None:
- raise CommandListError("Not in command list")
- self._writecommand("command_list_end")
- return self._getcommandlist()
-
-
-def escape(text):
- return text.replace("\\", "\\\\").replace('"', '\\"')
-
-
-# vim: set expandtab shiftwidth=4 softtabstop=4 textwidth=79: