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/>.
23 print('Failed to import asyncio, need python >= 3.4')
28 from os import environ
29 if 'DEBUG' in environ or 'PYTHONASYNCIODEBUG' in environ:
30 # environ['PYTHONASYNCIODEBUG'] = '1'
31 logging.basicConfig(level=logging.DEBUG)
33 HELLO_PREFIX = "OK MPD "
40 class MPDError(Exception):
43 class ConnectionError(MPDError):
46 class ProtocolError(MPDError):
49 class CommandError(MPDError):
59 return '"{0}…" ({1})'.format(
60 ' '.join(self.resp.split('\n')[:2]),
64 class MPDProto(asyncio.Protocol):
65 def __init__(self, future, payload):
66 logging.debug('payload: "%s"', payload)
69 self.payload = payload
70 self.sess = Response()
72 def connection_made(self, transport):
73 self.transport = transport
74 self.transport.write(bytes('{}\n'.format(self.payload), 'utf-8'))
76 def eof_received(self):
77 self.transport.close()
78 err = ConnectionError('Connection lost while reading line')
79 self.future.set_exception(err)
81 def data_received(self, data):
82 #logging.debug(data.decode('utf-8'))
83 rcv = self._hello(data.decode('utf-8'))
85 if rcv.startswith(ERROR_PREFIX):
86 err = rcv[len(ERROR_PREFIX):].strip()
87 self.future.set_exception(CommandError(err))
90 if rcv.endswith(SUCCESS+'\n'):
92 self.sess.resp = self.sess.resp[:len(SUCCESS+'\n')*-1]
93 logging.debug('set future result')
94 self.transport.close()
95 self.future.set_result(self.sess)
97 def _hello(self, rcv):
98 """Consume HELLO_PREFIX"""
99 if rcv.startswith(HELLO_PREFIX):
100 logging.debug('consumed hello prefix')
101 self.sess.version = rcv.split('\n')[0][len(HELLO_PREFIX):]
102 return rcv[rcv.find('\n')+1:]
107 :param string host: Server name or IP, default to 'localhost'
108 :param integer port: Server port, default to 6600
109 :param string passwd: Password, default to ``None``
113 def __init__(self, host='localhost', port=6600, passwd=None):
114 self._evloop = asyncio.get_event_loop()
119 #self._pwd = passwd # TODO: authentication yet to implement
128 def __getattr__(self, attr):
131 wrapper = self._command
132 if command not in self._commands:
133 command = command.replace("_", " ")
134 if command not in self._commands:
135 raise AttributeError("'%s' object has no attribute '%s'" %
136 (self.__class__.__name__, attr))
137 return lambda *args: wrapper(command, args)
139 async def _connect(self, proto):
140 # coroutine allowing Exception handling
141 # src: http://comments.gmane.org/gmane.comp.python.tulip/1401
143 await self._evloop.create_connection(lambda: proto,
146 except Exception as err:
147 proto.future.set_exception(ConnectionError(err))
149 def _command(self, command, args):
152 payload += ' "{}"'.format(escape(arg))
153 future = asyncio.Future()
154 # kick off a task to create the connection to MPD
155 coro = self._connect(MPDProto(future, payload))
157 self.futures.append(future)
159 # return once completed.
160 self._evloop.run_until_complete(future)
162 # alternative w/ callback
164 # future.add_done_callback(lambda ftr: MPDClient.loop.stop())
165 # self._evloop.run_forever()
169 """Run event loop gathering tasks from self.futures
172 self._evloop.run_until_complete(asyncio.gather(*self.futures))
175 logging.info('No task found in queue, need to set self.asio?')
179 """Escapting quotes and backslash"""
180 return text.replace('\\', '\\\\').replace('"', '\\"')