diff --git a/mopidy/frontends/mpd/__init__.py b/mopidy/frontends/mpd/__init__.py index b6088a41..d0ca761e 100644 --- a/mopidy/frontends/mpd/__init__.py +++ b/mopidy/frontends/mpd/__init__.py @@ -1,14 +1,19 @@ -import asyncore +import gobject import logging +import sys from pykka.actor import ThreadingActor from mopidy.frontends.base import BaseFrontend -from mopidy.frontends.mpd.server import MpdServer -from mopidy.utils.process import BaseThread +from mopidy import settings +from mopidy.utils import network +from mopidy.frontends.mpd.dispatcher import MpdDispatcher +from mopidy.frontends.mpd.protocol import ENCODING, VERSION, LINE_TERMINATOR +from mopidy.utils.log import indent logger = logging.getLogger('mopidy.frontends.mpd') +# FIXME no real need for frontend to be threading actor class MpdFrontend(ThreadingActor, BaseFrontend): """ The MPD frontend. @@ -25,22 +30,61 @@ class MpdFrontend(ThreadingActor, BaseFrontend): """ def __init__(self): - self._thread = None + hostname = network.format_hostname(settings.MPD_SERVER_HOSTNAME) + port = settings.MPD_SERVER_PORT + + try: + network.Listener(hostname, port, session=MpdSession) + except IOError, e: + logger.error(u'MPD server startup failed: %s', e) + sys.exit(1) + + logger.info(u'MPD server running at [%s]:%s', hostname, port) + + +class MpdSession(ThreadingActor): + """ + The MPD client session. Keeps track of a single client session. Any + requests from the client is passed on to the MPD request dispatcher. + """ + + def __init__(self, sock, addr): + self.sock = sock # Prevent premature GC of socket closing it + self.addr = addr + self.channel = gobject.IOChannel(sock.fileno()) + self.dispatcher = MpdDispatcher() def on_start(self): - self._thread = MpdThread() - self._thread.start() + try: + self.send_response([u'OK MPD %s' % VERSION]) + self.request_loop() + except gobject.GError: + self.stop() - def on_receive(self, message): - pass # Ignore any messages + def close(self): + self.channel.close() + def request_loop(self): + while True: + data = self.channel.readline() + if not data: + return self.close() + request = data.rstrip(LINE_TERMINATOR).decode(ENCODING) + logger.debug(u'Request from [%s]:%s: %s', self.addr[0], + self.addr[1], indent(request)) + response = self.dispatcher.handle_request(request) + self.send_response(response) -class MpdThread(BaseThread): - def __init__(self): - super(MpdThread, self).__init__() - self.name = u'MpdThread' - - def run_inside_try(self): - logger.debug(u'Starting MPD server thread') - server = MpdServer() - server.start() + def send_response(self, response): + """ + Format a response from the MPD command handlers and send it to the + client. + """ + if response: + response = LINE_TERMINATOR.join(response) + logger.debug(u'Response to [%s]:%s: %s', self.addr[0], + self.addr[1], indent(response)) + response = u'%s%s' % (response, LINE_TERMINATOR) + data = response.encode(ENCODING) + self.channel.write(data) + self.channel.flush() diff --git a/mopidy/frontends/mpd/server.py b/mopidy/frontends/mpd/server.py deleted file mode 100644 index 12e6a92a..00000000 --- a/mopidy/frontends/mpd/server.py +++ /dev/null @@ -1,76 +0,0 @@ -import logging -import sys - -import gobject - -from pykka.actor import ThreadingActor - -from mopidy import settings -from mopidy.utils import network -from mopidy.frontends.mpd.dispatcher import MpdDispatcher -from mopidy.frontends.mpd.protocol import ENCODING, LINE_TERMINATOR, VERSION -from mopidy.utils.log import indent - -logger = logging.getLogger('mopidy.frontends.mpd.server') - -class MpdServer(object): - """ - The MPD server. Creates a :class:`mopidy.frontends.mpd.session.MpdSession` - for each client connection. - """ - - def start(self): - """Start MPD server.""" - try: - hostname = network.format_hostname(settings.MPD_SERVER_HOSTNAME) - port = settings.MPD_SERVER_PORT - logger.debug(u'MPD server is binding to [%s]:%s', hostname, port) - network.Listener((hostname, port), session=MpdSession) - logger.info(u'MPD server running at [%s]:%s', hostname, port) - except IOError, e: - logger.error(u'MPD server startup failed: %s' % - str(e).decode('utf-8')) - sys.exit(1) - - -class MpdSession(ThreadingActor): - """ - The MPD client session. Keeps track of a single client session. Any - requests from the client is passed on to the MPD request dispatcher. - """ - - def __init__(self, sock, addr): - self.sock = sock # Prevent premature GC - self.addr = addr - self.channel = gobject.IOChannel(sock.fileno()) - self.dispatcher = MpdDispatcher(session=self) - - def on_start(self): - try: - self.send_response([u'OK MPD %s' % VERSION]) - self.request_loop() - except gobject.GError, e: - self.stop() - - def request_loop(self): - while True: - logger.debug('Trying to readline') - request = self.channel.readline()[:-1].decode(ENCODING) - logger.debug(u'Request from [%s]:%s: %s', self.addr[0], - self.addr[1], indent(request)) - response = self.dispatcher.handle_request(request) - self.send_response(response) - - def send_response(self, response): - """ - Format a response from the MPD command handlers and send it to the - client. - """ - if response: - response = LINE_TERMINATOR.join(response) - logger.debug(u'Response to [%s]:%s: %s', self.addr[0], - self.addr[1], indent(response)) - response = u'%s%s' % (response, LINE_TERMINATOR) - data = response.encode(ENCODING) - self.channel.write(data) - self.channel.flush() diff --git a/mopidy/utils/network.py b/mopidy/utils/network.py index df4f9292..d1536afb 100644 --- a/mopidy/utils/network.py +++ b/mopidy/utils/network.py @@ -39,15 +39,15 @@ def format_hostname(hostname): class Listener(object): """Setup listener and register it with gobject loop.""" - def __init__(self, addr, session): + def __init__(self, host, port, session): self.session = session self.sock = create_socket() self.sock.setblocking(0) - self.sock.bind(addr) + self.sock.bind((host, port)) self.sock.listen(5) gobject.io_add_watch(self.sock.fileno(), gobject.IO_IN, self.handle) - logger.debug('Listening on %s using %s', addr, self.session) + logger.debug('Listening on [%s]:%s using %s', host, port, self.session) def handle(self, fd, flags): sock, addr = self.sock.accept() diff --git a/tests/frontends/mpd/authentication_test.py b/tests/frontends/mpd/authentication_test.py index d795d726..7d340071 100644 --- a/tests/frontends/mpd/authentication_test.py +++ b/tests/frontends/mpd/authentication_test.py @@ -3,7 +3,7 @@ import unittest from mopidy import settings from mopidy.frontends.mpd.dispatcher import MpdDispatcher -from mopidy.frontends.mpd.session import MpdSession +from mopidy.frontends.mpd import MpdSession class AuthenticationTest(unittest.TestCase): def setUp(self): diff --git a/tests/frontends/mpd/connection_test.py b/tests/frontends/mpd/connection_test.py index bc995a5e..3f6b00f9 100644 --- a/tests/frontends/mpd/connection_test.py +++ b/tests/frontends/mpd/connection_test.py @@ -4,7 +4,7 @@ import unittest from mopidy import settings from mopidy.backends.dummy import DummyBackend from mopidy.frontends.mpd.dispatcher import MpdDispatcher -from mopidy.frontends.mpd.session import MpdSession +from mopidy.frontends.mpd import MpdSession from mopidy.mixers.dummy import DummyMixer class ConnectionHandlerTest(unittest.TestCase): diff --git a/tests/frontends/mpd/server_test.py b/tests/frontends/mpd/server_test.py deleted file mode 100644 index b2e27559..00000000 --- a/tests/frontends/mpd/server_test.py +++ /dev/null @@ -1,23 +0,0 @@ -import unittest - -from mopidy import settings -from mopidy.backends.dummy import DummyBackend -from mopidy.frontends.mpd import server -from mopidy.mixers.dummy import DummyMixer - -class MpdSessionTest(unittest.TestCase): - def setUp(self): - self.backend = DummyBackend.start().proxy() - self.mixer = DummyMixer.start().proxy() - self.session = server.MpdSession(None, None, (None, None)) - - def tearDown(self): - self.backend.stop().get() - self.mixer.stop().get() - settings.runtime.clear() - - def test_found_terminator_catches_decode_error(self): - # Pressing Ctrl+C in a telnet session sends a 0xff byte to the server. - self.session.input_buffer = ['\xff'] - self.session.found_terminator() - self.assertEqual(len(self.session.input_buffer), 0)