1 # -*- coding: utf-8 -*-
3 # python-musicpd: Python MPD client library
4 # Copyright (C) 2014-2015 Kaliko Jack <kaliko@azylum.org>
6 # python-musicpdasio is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU Lesser General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
11 # python-musicpd is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU Lesser General Public License for more details.
16 # You should have received a copy of the GNU Lesser General Public License
17 # along with python-musicpd. If not, see <http://www.gnu.org/licenses/>.
22 print('Failed to import asyncio, need python >= 3.4')
26 HELLO_PREFIX = "OK MPD "
33 class MPDError(Exception):
36 class ConnectionError(MPDError):
39 class ProtocolError(MPDError):
42 class CommandError(MPDError):
45 class CommandListError(MPDError):
48 class PendingCommandError(MPDError):
51 class IteratingError(MPDError):
62 return 'err:{0}, "{1}…" ({2})'.format(
64 ' '.join(self.resp.split('\n')[:2]),
67 class MPDProto(asyncio.Protocol):
68 def __init__(self, future, payload, cred):
71 self.payload = payload
72 self.sess = Response()
74 def connection_made(self, transport):
75 self.transport = transport
76 self.transport.write(bytes('{}\n'.format(self.payload), 'utf-8'))
78 def eof_received(self):
79 self.transport.close()
80 err = ConnectionError('Connection lost while reading line')
81 self.future.set_exception(err)
83 #def connection_lost(self):
86 def data_received(self, data):
87 rcv = self._hello(data.decode('utf-8'))
89 if rcv.startswith(ERROR_PREFIX):
90 err = rcv[len(ERROR_PREFIX):].strip()
91 self.future.set_exception(CommandError(err))
94 if rcv.endswith(SUCCESS+'\n'):
95 self.transport.close()
96 self.future.set_result(self.sess)
98 def _hello(self, rcv):
99 """Consume HELLO_PREFIX"""
101 if rcv.startswith(HELLO_PREFIX):
102 self.sess.version = rcv.split('\n')[0][len(HELLO_PREFIX):]
103 #print('consumed hello prefix: %s' % self.sess.version)
104 return rcv[rcv.find('\n')+1:]
108 loop = asyncio.get_event_loop()
110 def __init__(self, host='localhost', port=6600, cred=None):
120 def __getattr__(self, attr):
122 wrapper = self._command
123 if command not in self._commands:
124 command = command.replace("_", " ")
125 if command not in self._commands:
126 raise AttributeError("'%s' object has no attribute '%s'" %
127 (self.__class__.__name__, attr))
128 return lambda *args: wrapper(command, args)
130 def _command(self, command, args):
131 payload = '{} {}'.format(command ,''.join(args))
132 future = asyncio.Future()
133 # kick off a task to create the connection to MPD.
134 asyncio.async(MPDClient.loop.create_connection(
135 lambda: MPDProto(future, payload, self._cred),
138 MPDClient.loop.run_until_complete(future)
140 #if future.exception():
141 # raise future.exception()
142 # return once completed.
143 return future.result().resp