try:
import asyncio
except ImportError:
+ import sys
print('Failed to import asyncio, need python >= 3.4')
+ sys.exit(1)
-import sys
+import logging
+
+from os import environ
+if 'DEBUG' in environ or 'PYTHONASYNCIODEBUG' in environ:
+ logging.basicConfig(level=logging.DEBUG)
HELLO_PREFIX = "OK MPD "
ERROR_PREFIX = "ACK "
SUCCESS = "OK"
NEXT = "list_OK"
+VERSION = '0.0.1b'
class MPDError(Exception):
class IteratingError(MPDError):
pass
-loop = asyncio.get_event_loop()
class Response:
def __init__(self):
self.version = None
self.resp = ''
- self.err = None
def __repr__(self):
- return '{0}, {1}… ({2})'.format(self.err, self.resp[:15], self.version)
+ return '"{0}…" ({1})'.format(
+ ' '.join(self.resp.split('\n')[:2]),
+ self.version)
class MPDProto(asyncio.Protocol):
- def __init__(self, future, payload):
+ def __init__(self, future, payload, cred):
+ self.transport = None
self.future = future
self.payload = payload
self.sess = Response()
self.transport = transport
self.transport.write(bytes('{}\n'.format(self.payload), 'utf-8'))
- def data_received(self, data):
- rcv = data.decode('utf-8')
- if '\n' not in rcv:
- self.sess.err = 'Connection lost while reading line'
- self.future.set_result(self.sess)
- raise ConnectionError("Connection lost while reading line")
-
- rcv = rcv.strip('\n')
-
- if rcv.startswith(HELLO_PREFIX):
- self.sess.version = rcv[len(HELLO_PREFIX):]
- return
+ def eof_received(self):
self.transport.close()
+ err = ConnectionError('Connection lost while reading line')
+ self.future.set_exception(err)
- # set the result on the Future so that the main() coroutine can
- # resume
- if rcv.startswith(ERROR_PREFIX):
- self.sess.err = rcv[len(ERROR_PREFIX):].strip()
- self.future.set_result(self.sess)
- return
- else:
- self.sess.resp = rcv
- self.future.set_result(self.sess)
-
-def command(payload):
- future = asyncio.Future()
- if payload:
- cli = MPDProto(future, payload)
- # kick off a task to create the connection to MPD.
- asyncio.async(loop.create_connection(lambda: cli, host='192.168.0.20', port=6600))
- else:
- future.set_result((None, None, None))
+ def data_received(self, data):
+ #logging.debug(data.decode('utf-8'))
+ rcv = self._hello(data.decode('utf-8'))
- # return the future for the main() coroutine to wait on.
- return future
+ if rcv.startswith(ERROR_PREFIX):
+ err = rcv[len(ERROR_PREFIX):].strip()
+ self.future.set_exception(CommandError(err))
+ self.sess.resp += rcv
+ if rcv.endswith(SUCCESS+'\n'):
+ logging.debug('set future result')
+ self.transport.close()
+ self.future.set_result(self.sess)
-def main():
- """main"""
- import logging
- logging.basicConfig(level=logging.DEBUG)
- res = yield from command('currentsongt')
- if res.err:
- raise CommandError(res.err)
- print(res)
- print(res.resp)
-
-if __name__ == '__main__':
- loop.run_until_complete(main())
+ def _hello(self, rcv):
+ """Consume HELLO_PREFIX"""
+ if rcv.startswith(HELLO_PREFIX):
+ logging.debug('consumed hello prefix')
+ self.sess.version = rcv.split('\n')[0][len(HELLO_PREFIX):]
+ return rcv[rcv.find('\n')+1:]
+ return rcv
+
+class MPDClient:
+
+ def __init__(self, host='localhost', port=6600, cred=None):
+ self.eloop = asyncio.get_event_loop()
+ self.asio = False
+ self.futures = []
+ self._host = host
+ self._port = port
+ self._cred = cred
+ self._commands = {
+ 'currentsong',
+ 'stats',
+ 'playlistinfo',
+ }
+
+ def __getattr__(self, attr):
+ #logging.debug(attr)
+ command = attr
+ wrapper = self._command
+ if command not in self._commands:
+ command = command.replace("_", " ")
+ if command not in self._commands:
+ raise AttributeError("'%s' object has no attribute '%s'" %
+ (self.__class__.__name__, attr))
+ return lambda *args: wrapper(command, args)
+
+ @asyncio.coroutine
+ def _connect(self, proto):
+ # coroutine allowing Exception handling
+ # src: http://comments.gmane.org/gmane.comp.python.tulip/1401
+ try:
+ yield from self.eloop.create_connection(lambda: proto,
+ host=self._host,
+ port=self._port)
+ except Exception as err:
+ proto.future.set_exception(ConnectionError(err))
+
+ def _command(self, command, args):
+ payload = '{} {}'.format(command, ''.join(args))
+ future = asyncio.Future()
+ # kick off a task to create the connection to MPD
+ coro = self._connect(MPDProto(future, payload, self._cred))
+ asyncio.async(coro)
+ self.futures.append(future)
+ if not self.asio:
+ # return once completed.
+ self.eloop.run_until_complete(future)
+ return future
+ # alternative w/ callback
+ #if not self.asio:
+ # future.add_done_callback(lambda ftr: MPDClient.loop.stop())
+ # self.eloop.run_forever()
+ #return future
+
+ def run(self):
+ if self.futures:
+ self.eloop.run_until_complete(asyncio.gather(*self.futures))
+ self.futures = []
+ else:
+ logging.info('No task found in queue, need to set self.asio?')