From 10c1cab0c7ea2ee1b04d817974f6e826171ee3f7 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Thu, 19 Aug 2010 23:49:59 +0200 Subject: [PATCH 01/23] Reorganize mopidy.frontends.mpd - Rename .frontend.MpdFrontend to .dispatcher.MpdDispatcher, as it dispatches requests. - Move exceptions into .exceptions. - Move .server.MpdSession to .session.MpdSession. - Move handle_pattern to .protocol.handle_pattern. --- mopidy/frontends/mpd/__init__.py | 93 ------------------- .../mpd/{frontend.py => dispatcher.py} | 21 ++--- mopidy/frontends/mpd/exceptions.py | 57 ++++++++++++ mopidy/frontends/mpd/protocol/__init__.py | 36 +++++++ mopidy/frontends/mpd/protocol/audio_output.py | 3 +- mopidy/frontends/mpd/protocol/command_list.py | 3 +- mopidy/frontends/mpd/protocol/connection.py | 9 +- .../mpd/protocol/current_playlist.py | 3 +- mopidy/frontends/mpd/protocol/empty.py | 6 ++ mopidy/frontends/mpd/protocol/music_db.py | 4 +- mopidy/frontends/mpd/protocol/playback.py | 3 +- mopidy/frontends/mpd/protocol/reflection.py | 4 +- mopidy/frontends/mpd/protocol/status.py | 3 +- mopidy/frontends/mpd/protocol/stickers.py | 3 +- .../mpd/protocol/stored_playlists.py | 4 +- mopidy/frontends/mpd/server.py | 73 +-------------- mopidy/frontends/mpd/session.py | 71 ++++++++++++++ mopidy/settings.py | 4 +- tests/frontends/mpd/audio_output_test.py | 4 +- tests/frontends/mpd/command_list_test.py | 4 +- tests/frontends/mpd/connection_test.py | 4 +- tests/frontends/mpd/current_playlist_test.py | 4 +- ...est_handler_test.py => dispatcher_test.py} | 16 ++-- tests/frontends/mpd/exception_test.py | 2 +- tests/frontends/mpd/music_db_test.py | 4 +- tests/frontends/mpd/playback_test.py | 6 +- tests/frontends/mpd/reflection_test.py | 4 +- tests/frontends/mpd/status_test.py | 48 +++++----- tests/frontends/mpd/stickers_test.py | 4 +- tests/frontends/mpd/stored_playlists_test.py | 4 +- 30 files changed, 258 insertions(+), 246 deletions(-) rename mopidy/frontends/mpd/{frontend.py => dispatcher.py} (82%) create mode 100644 mopidy/frontends/mpd/exceptions.py create mode 100644 mopidy/frontends/mpd/protocol/empty.py create mode 100644 mopidy/frontends/mpd/session.py rename tests/frontends/mpd/{request_handler_test.py => dispatcher_test.py} (75%) diff --git a/mopidy/frontends/mpd/__init__.py b/mopidy/frontends/mpd/__init__.py index 83d6ce4c..e69de29b 100644 --- a/mopidy/frontends/mpd/__init__.py +++ b/mopidy/frontends/mpd/__init__.py @@ -1,93 +0,0 @@ -import re - -from mopidy import MopidyException - -class MpdAckError(MopidyException): - """ - Available MPD error codes:: - - ACK_ERROR_NOT_LIST = 1 - ACK_ERROR_ARG = 2 - ACK_ERROR_PASSWORD = 3 - ACK_ERROR_PERMISSION = 4 - ACK_ERROR_UNKNOWN = 5 - ACK_ERROR_NO_EXIST = 50 - ACK_ERROR_PLAYLIST_MAX = 51 - ACK_ERROR_SYSTEM = 52 - ACK_ERROR_PLAYLIST_LOAD = 53 - ACK_ERROR_UPDATE_ALREADY = 54 - ACK_ERROR_PLAYER_SYNC = 55 - ACK_ERROR_EXIST = 56 - """ - - def __init__(self, message=u'', error_code=0, index=0, command=u''): - super(MpdAckError, self).__init__(message, error_code, index, command) - self.message = message - self.error_code = error_code - self.index = index - self.command = command - - def get_mpd_ack(self): - """ - MPD error code format:: - - ACK [%(error_code)i@%(index)i] {%(command)s} description - """ - return u'ACK [%i@%i] {%s} %s' % ( - self.error_code, self.index, self.command, self.message) - -class MpdArgError(MpdAckError): - def __init__(self, *args, **kwargs): - super(MpdArgError, self).__init__(*args, **kwargs) - self.error_code = 2 # ACK_ERROR_ARG - -class MpdUnknownCommand(MpdAckError): - def __init__(self, *args, **kwargs): - super(MpdUnknownCommand, self).__init__(*args, **kwargs) - self.message = u'unknown command "%s"' % self.command - self.command = u'' - self.error_code = 5 # ACK_ERROR_UNKNOWN - -class MpdNoExistError(MpdAckError): - def __init__(self, *args, **kwargs): - super(MpdNoExistError, self).__init__(*args, **kwargs) - self.error_code = 50 # ACK_ERROR_NO_EXIST - -class MpdNotImplemented(MpdAckError): - def __init__(self, *args, **kwargs): - super(MpdNotImplemented, self).__init__(*args, **kwargs) - self.message = u'Not implemented' - -mpd_commands = set() -request_handlers = {} - -def handle_pattern(pattern): - """ - Decorator for connecting command handlers to command patterns. - - If you use named groups in the pattern, the decorated method will get the - groups as keyword arguments. If the group is optional, remember to give the - argument a default value. - - For example, if the command is ``do that thing`` the ``what`` argument will - be ``this thing``:: - - @handle_pattern('^do (?P.+)$') - def do(what): - ... - - :param pattern: regexp pattern for matching commands - :type pattern: string - """ - def decorator(func): - match = re.search('([a-z_]+)', pattern) - if match is not None: - mpd_commands.add(match.group()) - if pattern in request_handlers: - raise ValueError(u'Tried to redefine handler for %s with %s' % ( - pattern, func)) - request_handlers[pattern] = func - func.__doc__ = ' - *Pattern:* ``%s``\n\n%s' % ( - pattern, func.__doc__ or '') - return func - return decorator diff --git a/mopidy/frontends/mpd/frontend.py b/mopidy/frontends/mpd/dispatcher.py similarity index 82% rename from mopidy/frontends/mpd/frontend.py rename to mopidy/frontends/mpd/dispatcher.py index 9a0251eb..2a477e1c 100644 --- a/mopidy/frontends/mpd/frontend.py +++ b/mopidy/frontends/mpd/dispatcher.py @@ -1,20 +1,18 @@ -import logging import re -from mopidy.frontends.mpd import (mpd_commands, request_handlers, - handle_pattern, MpdAckError, MpdArgError, MpdUnknownCommand) +from mopidy.frontends.mpd.exceptions import (MpdAckError, MpdArgError, + MpdUnknownCommand) +from mopidy.frontends.mpd.protocol import mpd_commands, request_handlers # Do not remove the following import. The protocol modules must be imported to # get them registered as request handlers. from mopidy.frontends.mpd.protocol import (audio_output, command_list, - connection, current_playlist, music_db, playback, reflection, status, - stickers, stored_playlists) + connection, current_playlist, empty, music_db, playback, reflection, + status, stickers, stored_playlists) from mopidy.utils import flatten -logger = logging.getLogger('mopidy.frontends.mpd.frontend') - -class MpdFrontend(object): +class MpdDispatcher(object): """ - The MPD frontend dispatches MPD requests to the correct handler. + Dispatches MPD requests to the correct handler. """ def __init__(self, backend=None): @@ -72,8 +70,3 @@ class MpdFrontend(object): if add_ok and (not response or not response[-1].startswith(u'ACK')): response.append(u'OK') return response - -@handle_pattern(r'^$') -def empty(frontend): - """The original MPD server returns ``OK`` on an empty request.""" - pass diff --git a/mopidy/frontends/mpd/exceptions.py b/mopidy/frontends/mpd/exceptions.py new file mode 100644 index 00000000..2a18b2f3 --- /dev/null +++ b/mopidy/frontends/mpd/exceptions.py @@ -0,0 +1,57 @@ +from mopidy import MopidyException + +class MpdAckError(MopidyException): + """ + Available MPD error codes:: + + ACK_ERROR_NOT_LIST = 1 + ACK_ERROR_ARG = 2 + ACK_ERROR_PASSWORD = 3 + ACK_ERROR_PERMISSION = 4 + ACK_ERROR_UNKNOWN = 5 + ACK_ERROR_NO_EXIST = 50 + ACK_ERROR_PLAYLIST_MAX = 51 + ACK_ERROR_SYSTEM = 52 + ACK_ERROR_PLAYLIST_LOAD = 53 + ACK_ERROR_UPDATE_ALREADY = 54 + ACK_ERROR_PLAYER_SYNC = 55 + ACK_ERROR_EXIST = 56 + """ + + def __init__(self, message=u'', error_code=0, index=0, command=u''): + super(MpdAckError, self).__init__(message, error_code, index, command) + self.message = message + self.error_code = error_code + self.index = index + self.command = command + + def get_mpd_ack(self): + """ + MPD error code format:: + + ACK [%(error_code)i@%(index)i] {%(command)s} description + """ + return u'ACK [%i@%i] {%s} %s' % ( + self.error_code, self.index, self.command, self.message) + +class MpdArgError(MpdAckError): + def __init__(self, *args, **kwargs): + super(MpdArgError, self).__init__(*args, **kwargs) + self.error_code = 2 # ACK_ERROR_ARG + +class MpdUnknownCommand(MpdAckError): + def __init__(self, *args, **kwargs): + super(MpdUnknownCommand, self).__init__(*args, **kwargs) + self.message = u'unknown command "%s"' % self.command + self.command = u'' + self.error_code = 5 # ACK_ERROR_UNKNOWN + +class MpdNoExistError(MpdAckError): + def __init__(self, *args, **kwargs): + super(MpdNoExistError, self).__init__(*args, **kwargs) + self.error_code = 50 # ACK_ERROR_NO_EXIST + +class MpdNotImplemented(MpdAckError): + def __init__(self, *args, **kwargs): + super(MpdNotImplemented, self).__init__(*args, **kwargs) + self.message = u'Not implemented' diff --git a/mopidy/frontends/mpd/protocol/__init__.py b/mopidy/frontends/mpd/protocol/__init__.py index 00932e90..c27bf0de 100644 --- a/mopidy/frontends/mpd/protocol/__init__.py +++ b/mopidy/frontends/mpd/protocol/__init__.py @@ -10,8 +10,44 @@ implement our own MPD server which is compatible with the numerous existing `MPD clients `_. """ +import re + #: The MPD protocol uses UTF-8 for encoding all data. ENCODING = u'utf-8' #: The MPD protocol uses ``\n`` as line terminator. LINE_TERMINATOR = u'\n' + +mpd_commands = set() +request_handlers = {} + +def handle_pattern(pattern): + """ + Decorator for connecting command handlers to command patterns. + + If you use named groups in the pattern, the decorated method will get the + groups as keyword arguments. If the group is optional, remember to give the + argument a default value. + + For example, if the command is ``do that thing`` the ``what`` argument will + be ``this thing``:: + + @handle_pattern('^do (?P.+)$') + def do(what): + ... + + :param pattern: regexp pattern for matching commands + :type pattern: string + """ + def decorator(func): + match = re.search('([a-z_]+)', pattern) + if match is not None: + mpd_commands.add(match.group()) + if pattern in request_handlers: + raise ValueError(u'Tried to redefine handler for %s with %s' % ( + pattern, func)) + request_handlers[pattern] = func + func.__doc__ = ' - *Pattern:* ``%s``\n\n%s' % ( + pattern, func.__doc__ or '') + return func + return decorator diff --git a/mopidy/frontends/mpd/protocol/audio_output.py b/mopidy/frontends/mpd/protocol/audio_output.py index e659b162..d25fc118 100644 --- a/mopidy/frontends/mpd/protocol/audio_output.py +++ b/mopidy/frontends/mpd/protocol/audio_output.py @@ -1,4 +1,5 @@ -from mopidy.frontends.mpd import handle_pattern, MpdNotImplemented +from mopidy.frontends.mpd.protocol import handle_pattern +from mopidy.frontends.mpd.exceptions import MpdNotImplemented @handle_pattern(r'^disableoutput "(?P\d+)"$') def disableoutput(frontend, outputid): diff --git a/mopidy/frontends/mpd/protocol/command_list.py b/mopidy/frontends/mpd/protocol/command_list.py index 900c26b0..b3df0be6 100644 --- a/mopidy/frontends/mpd/protocol/command_list.py +++ b/mopidy/frontends/mpd/protocol/command_list.py @@ -1,4 +1,5 @@ -from mopidy.frontends.mpd import handle_pattern, MpdUnknownCommand +from mopidy.frontends.mpd.protocol import handle_pattern +from mopidy.frontends.mpd.exceptions import MpdUnknownCommand @handle_pattern(r'^command_list_begin$') def command_list_begin(frontend): diff --git a/mopidy/frontends/mpd/protocol/connection.py b/mopidy/frontends/mpd/protocol/connection.py index 4312ded5..0ce3ef51 100644 --- a/mopidy/frontends/mpd/protocol/connection.py +++ b/mopidy/frontends/mpd/protocol/connection.py @@ -1,4 +1,5 @@ -from mopidy.frontends.mpd import handle_pattern, MpdNotImplemented +from mopidy.frontends.mpd.protocol import handle_pattern +from mopidy.frontends.mpd.exceptions import MpdNotImplemented @handle_pattern(r'^close$') def close(frontend): @@ -9,8 +10,7 @@ def close(frontend): Closes the connection to MPD. """ - # TODO Does not work after multiprocessing branch merge - #frontend.session.do_close() + pass # TODO @handle_pattern(r'^kill$') def kill(frontend): @@ -21,8 +21,7 @@ def kill(frontend): Kills MPD. """ - # TODO Does not work after multiprocessing branch merge - #frontend.session.do_kill() + pass # TODO @handle_pattern(r'^password "(?P[^"]+)"$') def password_(frontend, password): diff --git a/mopidy/frontends/mpd/protocol/current_playlist.py b/mopidy/frontends/mpd/protocol/current_playlist.py index b9111d9e..90a53f5f 100644 --- a/mopidy/frontends/mpd/protocol/current_playlist.py +++ b/mopidy/frontends/mpd/protocol/current_playlist.py @@ -1,4 +1,5 @@ -from mopidy.frontends.mpd import (handle_pattern, MpdArgError, MpdNoExistError, +from mopidy.frontends.mpd.protocol import handle_pattern +from mopidy.frontends.mpd.exceptions import (MpdArgError, MpdNoExistError, MpdNotImplemented) @handle_pattern(r'^add "(?P[^"]*)"$') diff --git a/mopidy/frontends/mpd/protocol/empty.py b/mopidy/frontends/mpd/protocol/empty.py new file mode 100644 index 00000000..a39d79eb --- /dev/null +++ b/mopidy/frontends/mpd/protocol/empty.py @@ -0,0 +1,6 @@ +from mopidy.frontends.mpd.protocol import handle_pattern + +@handle_pattern(r'^$') +def empty(frontend): + """The original MPD server returns ``OK`` on an empty request.""" + pass diff --git a/mopidy/frontends/mpd/protocol/music_db.py b/mopidy/frontends/mpd/protocol/music_db.py index 5aec6eae..d4dcf50d 100644 --- a/mopidy/frontends/mpd/protocol/music_db.py +++ b/mopidy/frontends/mpd/protocol/music_db.py @@ -1,7 +1,7 @@ import re -from mopidy.frontends.mpd import handle_pattern, MpdNotImplemented -from mopidy.frontends.mpd.protocol import stored_playlists +from mopidy.frontends.mpd.protocol import handle_pattern, stored_playlists +from mopidy.frontends.mpd.exceptions import MpdNotImplemented def _build_query(mpd_query): """ diff --git a/mopidy/frontends/mpd/protocol/playback.py b/mopidy/frontends/mpd/protocol/playback.py index 7abc4509..762e2da7 100644 --- a/mopidy/frontends/mpd/protocol/playback.py +++ b/mopidy/frontends/mpd/protocol/playback.py @@ -1,4 +1,5 @@ -from mopidy.frontends.mpd import (handle_pattern, MpdArgError, MpdNoExistError, +from mopidy.frontends.mpd.protocol import handle_pattern +from mopidy.frontends.mpd.exceptions import (MpdArgError, MpdNoExistError, MpdNotImplemented) @handle_pattern(r'^consume (?P[01])$') diff --git a/mopidy/frontends/mpd/protocol/reflection.py b/mopidy/frontends/mpd/protocol/reflection.py index 0c349746..d2c9c599 100644 --- a/mopidy/frontends/mpd/protocol/reflection.py +++ b/mopidy/frontends/mpd/protocol/reflection.py @@ -1,5 +1,5 @@ -from mopidy.frontends.mpd import (handle_pattern, mpd_commands, - MpdNotImplemented) +from mopidy.frontends.mpd.protocol import handle_pattern, mpd_commands +from mopidy.frontends.mpd.exceptions import MpdNotImplemented @handle_pattern(r'^commands$') def commands(frontend): diff --git a/mopidy/frontends/mpd/protocol/status.py b/mopidy/frontends/mpd/protocol/status.py index 16e73dea..e18f1ea4 100644 --- a/mopidy/frontends/mpd/protocol/status.py +++ b/mopidy/frontends/mpd/protocol/status.py @@ -1,4 +1,5 @@ -from mopidy.frontends.mpd import handle_pattern, MpdNotImplemented +from mopidy.frontends.mpd.protocol import handle_pattern +from mopidy.frontends.mpd.exceptions import MpdNotImplemented @handle_pattern(r'^clearerror$') def clearerror(frontend): diff --git a/mopidy/frontends/mpd/protocol/stickers.py b/mopidy/frontends/mpd/protocol/stickers.py index c184d1f9..145665eb 100644 --- a/mopidy/frontends/mpd/protocol/stickers.py +++ b/mopidy/frontends/mpd/protocol/stickers.py @@ -1,4 +1,5 @@ -from mopidy.frontends.mpd import handle_pattern, MpdNotImplemented +from mopidy.frontends.mpd.protocol import handle_pattern +from mopidy.frontends.mpd.exceptions import MpdNotImplemented @handle_pattern(r'^sticker delete "(?P[^"]+)" ' r'"(?P[^"]+)"( "(?P[^"]+)")*$') diff --git a/mopidy/frontends/mpd/protocol/stored_playlists.py b/mopidy/frontends/mpd/protocol/stored_playlists.py index 3d7a8710..c34b1676 100644 --- a/mopidy/frontends/mpd/protocol/stored_playlists.py +++ b/mopidy/frontends/mpd/protocol/stored_playlists.py @@ -1,7 +1,7 @@ import datetime as dt -from mopidy.frontends.mpd import (handle_pattern, MpdNoExistError, - MpdNotImplemented) +from mopidy.frontends.mpd.protocol import handle_pattern +from mopidy.frontends.mpd.exceptions import MpdNoExistError, MpdNotImplemented @handle_pattern(r'^listplaylist "(?P[^"]+)"$') def listplaylist(frontend, name): diff --git a/mopidy/frontends/mpd/server.py b/mopidy/frontends/mpd/server.py index 91d8e67a..db13e516 100644 --- a/mopidy/frontends/mpd/server.py +++ b/mopidy/frontends/mpd/server.py @@ -1,21 +1,18 @@ -import asynchat import asyncore import logging -import multiprocessing import re import socket import sys -from mopidy import get_mpd_protocol_version, settings -from mopidy.frontends.mpd.protocol import ENCODING, LINE_TERMINATOR -from mopidy.process import pickle_connection -from mopidy.utils import indent +from mopidy import settings +from .session import MpdSession logger = logging.getLogger('mopidy.frontends.mpd.server') class MpdServer(asyncore.dispatcher): """ - The MPD server. Creates a :class:`MpdSession` for each client connection. + The MPD server. Creates a :class:`mopidy.frontends.mpd.session.MpdSession` + for each client connection. """ def __init__(self, core_queue): @@ -58,65 +55,3 @@ class MpdServer(asyncore.dispatcher): and re.match('\d+.\d+.\d+.\d+', hostname) is not None): hostname = '::ffff:%s' % hostname return hostname - - -class MpdSession(asynchat.async_chat): - """ - The MPD client session. Keeps track of a single client and dispatches its - MPD requests to the frontend. - """ - - def __init__(self, server, client_socket, client_socket_address, - core_queue): - asynchat.async_chat.__init__(self, sock=client_socket) - self.server = server - self.client_address = client_socket_address[0] - self.client_port = client_socket_address[1] - self.core_queue = core_queue - self.input_buffer = [] - self.set_terminator(LINE_TERMINATOR.encode(ENCODING)) - - def start(self): - """Start a new client session.""" - self.send_response(u'OK MPD %s' % get_mpd_protocol_version()) - - def collect_incoming_data(self, data): - """Collect incoming data into buffer until a terminator is found.""" - self.input_buffer.append(data) - - def found_terminator(self): - """Handle request when a terminator is found.""" - data = ''.join(self.input_buffer).strip() - self.input_buffer = [] - try: - request = data.decode(ENCODING) - logger.debug(u'Input from [%s]:%s: %s', self.client_address, - self.client_port, indent(request)) - self.handle_request(request) - except UnicodeDecodeError as e: - logger.warning(u'Received invalid data: %s', e) - - def handle_request(self, request): - """Handle request by sending it to the MPD frontend.""" - my_end, other_end = multiprocessing.Pipe() - self.core_queue.put({ - 'command': 'mpd_request', - 'request': request, - 'reply_to': pickle_connection(other_end), - }) - my_end.poll(None) - response = my_end.recv() - if response is not None: - self.handle_response(response) - - def handle_response(self, response): - """Handle response from the MPD frontend.""" - self.send_response(LINE_TERMINATOR.join(response)) - - def send_response(self, output): - """Send a response to the client.""" - logger.debug(u'Output to [%s]:%s: %s', self.client_address, - self.client_port, indent(output)) - output = u'%s%s' % (output, LINE_TERMINATOR) - data = output.encode(ENCODING) - self.push(data) diff --git a/mopidy/frontends/mpd/session.py b/mopidy/frontends/mpd/session.py new file mode 100644 index 00000000..0a7533e7 --- /dev/null +++ b/mopidy/frontends/mpd/session.py @@ -0,0 +1,71 @@ +import asynchat +import logging +import multiprocessing + +from mopidy import get_mpd_protocol_version +from mopidy.process import pickle_connection +from mopidy.utils import indent +from .protocol import ENCODING, LINE_TERMINATOR + +logger = logging.getLogger('mopidy.frontends.mpd.session') + +class MpdSession(asynchat.async_chat): + """ + The MPD client session. Keeps track of a single client and dispatches its + MPD requests to the frontend. + """ + + def __init__(self, server, client_socket, client_socket_address, + core_queue): + asynchat.async_chat.__init__(self, sock=client_socket) + self.server = server + self.client_address = client_socket_address[0] + self.client_port = client_socket_address[1] + self.core_queue = core_queue + self.input_buffer = [] + self.set_terminator(LINE_TERMINATOR.encode(ENCODING)) + + def start(self): + """Start a new client session.""" + self.send_response(u'OK MPD %s' % get_mpd_protocol_version()) + + def collect_incoming_data(self, data): + """Collect incoming data into buffer until a terminator is found.""" + self.input_buffer.append(data) + + def found_terminator(self): + """Handle request when a terminator is found.""" + data = ''.join(self.input_buffer).strip() + self.input_buffer = [] + try: + request = data.decode(ENCODING) + logger.debug(u'Input from [%s]:%s: %s', self.client_address, + self.client_port, indent(request)) + self.handle_request(request) + except UnicodeDecodeError as e: + logger.warning(u'Received invalid data: %s', e) + + def handle_request(self, request): + """Handle request by sending it to the MPD frontend.""" + my_end, other_end = multiprocessing.Pipe() + self.core_queue.put({ + 'command': 'mpd_request', + 'request': request, + 'reply_to': pickle_connection(other_end), + }) + my_end.poll(None) + response = my_end.recv() + if response is not None: + self.handle_response(response) + + def handle_response(self, response): + """Handle response from the MPD frontend.""" + self.send_response(LINE_TERMINATOR.join(response)) + + def send_response(self, output): + """Send a response to the client.""" + logger.debug(u'Output to [%s]:%s: %s', self.client_address, + self.client_port, indent(output)) + output = u'%s%s' % (output, LINE_TERMINATOR) + data = output.encode(ENCODING) + self.push(data) diff --git a/mopidy/settings.py b/mopidy/settings.py index c9e3606e..895a1e24 100644 --- a/mopidy/settings.py +++ b/mopidy/settings.py @@ -45,8 +45,8 @@ DUMP_LOG_FILENAME = u'dump.log' #: #: Default:: #: -#: FRONTEND = u'mopidy.frontends.mpd.frontend.MpdFrontend' -FRONTEND = u'mopidy.frontends.mpd.frontend.MpdFrontend' +#: FRONTEND = u'mopidy.frontends.mpd.dispatcher.MpdDispatcher' +FRONTEND = u'mopidy.frontends.mpd.dispatcher.MpdDispatcher' #: Path to folder with local music. #: diff --git a/tests/frontends/mpd/audio_output_test.py b/tests/frontends/mpd/audio_output_test.py index 24201341..b81e727e 100644 --- a/tests/frontends/mpd/audio_output_test.py +++ b/tests/frontends/mpd/audio_output_test.py @@ -1,13 +1,13 @@ import unittest from mopidy.backends.dummy import DummyBackend -from mopidy.frontends.mpd import frontend +from mopidy.frontends.mpd import dispatcher from mopidy.mixers.dummy import DummyMixer class AudioOutputHandlerTest(unittest.TestCase): def setUp(self): self.b = DummyBackend(mixer_class=DummyMixer) - self.h = frontend.MpdFrontend(backend=self.b) + self.h = dispatcher.MpdDispatcher(backend=self.b) def test_enableoutput(self): result = self.h.handle_request(u'enableoutput "0"') diff --git a/tests/frontends/mpd/command_list_test.py b/tests/frontends/mpd/command_list_test.py index 683a1013..6c801c3f 100644 --- a/tests/frontends/mpd/command_list_test.py +++ b/tests/frontends/mpd/command_list_test.py @@ -1,13 +1,13 @@ import unittest from mopidy.backends.dummy import DummyBackend -from mopidy.frontends.mpd import frontend +from mopidy.frontends.mpd import dispatcher from mopidy.mixers.dummy import DummyMixer class CommandListsTest(unittest.TestCase): def setUp(self): self.b = DummyBackend(mixer_class=DummyMixer) - self.h = frontend.MpdFrontend(backend=self.b) + self.h = dispatcher.MpdDispatcher(backend=self.b) def test_command_list_begin(self): result = self.h.handle_request(u'command_list_begin') diff --git a/tests/frontends/mpd/connection_test.py b/tests/frontends/mpd/connection_test.py index 341e630c..21753054 100644 --- a/tests/frontends/mpd/connection_test.py +++ b/tests/frontends/mpd/connection_test.py @@ -1,13 +1,13 @@ import unittest from mopidy.backends.dummy import DummyBackend -from mopidy.frontends.mpd import frontend +from mopidy.frontends.mpd import dispatcher from mopidy.mixers.dummy import DummyMixer class ConnectionHandlerTest(unittest.TestCase): def setUp(self): self.b = DummyBackend(mixer_class=DummyMixer) - self.h = frontend.MpdFrontend(backend=self.b) + self.h = dispatcher.MpdDispatcher(backend=self.b) def test_close(self): result = self.h.handle_request(u'close') diff --git a/tests/frontends/mpd/current_playlist_test.py b/tests/frontends/mpd/current_playlist_test.py index e27e58c5..c53e2b8d 100644 --- a/tests/frontends/mpd/current_playlist_test.py +++ b/tests/frontends/mpd/current_playlist_test.py @@ -1,14 +1,14 @@ import unittest from mopidy.backends.dummy import DummyBackend -from mopidy.frontends.mpd import frontend +from mopidy.frontends.mpd import dispatcher from mopidy.mixers.dummy import DummyMixer from mopidy.models import Track class CurrentPlaylistHandlerTest(unittest.TestCase): def setUp(self): self.b = DummyBackend(mixer_class=DummyMixer) - self.h = frontend.MpdFrontend(backend=self.b) + self.h = dispatcher.MpdDispatcher(backend=self.b) def test_add(self): needle = Track(uri='dummy://foo') diff --git a/tests/frontends/mpd/request_handler_test.py b/tests/frontends/mpd/dispatcher_test.py similarity index 75% rename from tests/frontends/mpd/request_handler_test.py rename to tests/frontends/mpd/dispatcher_test.py index ac8bd7e9..2a2ee4db 100644 --- a/tests/frontends/mpd/request_handler_test.py +++ b/tests/frontends/mpd/dispatcher_test.py @@ -1,19 +1,21 @@ import unittest from mopidy.backends.dummy import DummyBackend -from mopidy.frontends.mpd import frontend, MpdAckError +from mopidy.frontends.mpd import dispatcher +from mopidy.frontends.mpd.exceptions import MpdAckError +from mopidy.frontends.mpd.protocol import request_handlers, handle_pattern from mopidy.mixers.dummy import DummyMixer -class RequestHandlerTest(unittest.TestCase): +class MpdDispatcherTest(unittest.TestCase): def setUp(self): self.b = DummyBackend(mixer_class=DummyMixer) - self.h = frontend.MpdFrontend(backend=self.b) + self.h = dispatcher.MpdDispatcher(backend=self.b) def test_register_same_pattern_twice_fails(self): func = lambda: None try: - frontend.handle_pattern('a pattern')(func) - frontend.handle_pattern('a pattern')(func) + handle_pattern('a pattern')(func) + handle_pattern('a pattern')(func) self.fail('Registering a pattern twice shoulde raise ValueError') except ValueError: pass @@ -28,7 +30,7 @@ class RequestHandlerTest(unittest.TestCase): def test_finding_handler_for_known_command_returns_handler_and_kwargs(self): expected_handler = lambda x: None - frontend.request_handlers['known_command (?P.+)'] = \ + request_handlers['known_command (?P.+)'] = \ expected_handler (handler, kwargs) = self.h.find_handler('known_command an_arg') self.assertEqual(handler, expected_handler) @@ -41,7 +43,7 @@ class RequestHandlerTest(unittest.TestCase): def test_handling_known_request(self): expected = 'magic' - frontend.request_handlers['known request'] = lambda x: expected + request_handlers['known request'] = lambda x: expected result = self.h.handle_request('known request') self.assert_(u'OK' in result) self.assert_(expected in result) diff --git a/tests/frontends/mpd/exception_test.py b/tests/frontends/mpd/exception_test.py index e337550f..ef222d46 100644 --- a/tests/frontends/mpd/exception_test.py +++ b/tests/frontends/mpd/exception_test.py @@ -1,6 +1,6 @@ import unittest -from mopidy.frontends.mpd import (MpdAckError, MpdUnknownCommand, +from mopidy.frontends.mpd.exceptions import (MpdAckError, MpdUnknownCommand, MpdNotImplemented) class MpdExceptionsTest(unittest.TestCase): diff --git a/tests/frontends/mpd/music_db_test.py b/tests/frontends/mpd/music_db_test.py index fc8f980a..5fcc393c 100644 --- a/tests/frontends/mpd/music_db_test.py +++ b/tests/frontends/mpd/music_db_test.py @@ -1,13 +1,13 @@ import unittest from mopidy.backends.dummy import DummyBackend -from mopidy.frontends.mpd import frontend +from mopidy.frontends.mpd import dispatcher from mopidy.mixers.dummy import DummyMixer class MusicDatabaseHandlerTest(unittest.TestCase): def setUp(self): self.b = DummyBackend(mixer_class=DummyMixer) - self.h = frontend.MpdFrontend(backend=self.b) + self.h = dispatcher.MpdDispatcher(backend=self.b) def test_count(self): result = self.h.handle_request(u'count "tag" "needle"') diff --git a/tests/frontends/mpd/playback_test.py b/tests/frontends/mpd/playback_test.py index 17263aef..3ba48a54 100644 --- a/tests/frontends/mpd/playback_test.py +++ b/tests/frontends/mpd/playback_test.py @@ -1,14 +1,14 @@ import unittest from mopidy.backends.dummy import DummyBackend -from mopidy.frontends.mpd import frontend +from mopidy.frontends.mpd import dispatcher from mopidy.mixers.dummy import DummyMixer from mopidy.models import Track class PlaybackOptionsHandlerTest(unittest.TestCase): def setUp(self): self.b = DummyBackend(mixer_class=DummyMixer) - self.h = frontend.MpdFrontend(backend=self.b) + self.h = dispatcher.MpdDispatcher(backend=self.b) def test_consume_off(self): result = self.h.handle_request(u'consume "0"') @@ -167,7 +167,7 @@ class PlaybackOptionsHandlerTest(unittest.TestCase): class PlaybackControlHandlerTest(unittest.TestCase): def setUp(self): self.b = DummyBackend(mixer_class=DummyMixer) - self.h = frontend.MpdFrontend(backend=self.b) + self.h = dispatcher.MpdDispatcher(backend=self.b) def test_next(self): result = self.h.handle_request(u'next') diff --git a/tests/frontends/mpd/reflection_test.py b/tests/frontends/mpd/reflection_test.py index 5491946c..a4491d75 100644 --- a/tests/frontends/mpd/reflection_test.py +++ b/tests/frontends/mpd/reflection_test.py @@ -1,13 +1,13 @@ import unittest from mopidy.backends.dummy import DummyBackend -from mopidy.frontends.mpd import frontend +from mopidy.frontends.mpd import dispatcher from mopidy.mixers.dummy import DummyMixer class ReflectionHandlerTest(unittest.TestCase): def setUp(self): self.b = DummyBackend(mixer_class=DummyMixer) - self.h = frontend.MpdFrontend(backend=self.b) + self.h = dispatcher.MpdDispatcher(backend=self.b) def test_commands_returns_list_of_all_commands(self): result = self.h.handle_request(u'commands') diff --git a/tests/frontends/mpd/status_test.py b/tests/frontends/mpd/status_test.py index 9839acfe..fbd0ff9e 100644 --- a/tests/frontends/mpd/status_test.py +++ b/tests/frontends/mpd/status_test.py @@ -1,14 +1,14 @@ import unittest from mopidy.backends.dummy import DummyBackend -from mopidy.frontends.mpd import frontend +from mopidy.frontends.mpd import dispatcher from mopidy.mixers.dummy import DummyMixer from mopidy.models import Track class StatusHandlerTest(unittest.TestCase): def setUp(self): self.b = DummyBackend(mixer_class=DummyMixer) - self.h = frontend.MpdFrontend(backend=self.b) + self.h = dispatcher.MpdDispatcher(backend=self.b) def test_clearerror(self): result = self.h.handle_request(u'clearerror') @@ -51,7 +51,7 @@ class StatusHandlerTest(unittest.TestCase): self.assert_(u'OK' in result) def test_stats_method(self): - result = frontend.status.stats(self.h) + result = dispatcher.status.stats(self.h) self.assert_('artists' in result) self.assert_(int(result['artists']) >= 0) self.assert_('albums' in result) @@ -72,106 +72,106 @@ class StatusHandlerTest(unittest.TestCase): self.assert_(u'OK' in result) def test_status_method_contains_volume_which_defaults_to_0(self): - result = dict(frontend.status.status(self.h)) + result = dict(dispatcher.status.status(self.h)) self.assert_('volume' in result) self.assertEqual(int(result['volume']), 0) def test_status_method_contains_volume(self): self.b.mixer.volume = 17 - result = dict(frontend.status.status(self.h)) + result = dict(dispatcher.status.status(self.h)) self.assert_('volume' in result) self.assertEqual(int(result['volume']), 17) def test_status_method_contains_repeat_is_0(self): - result = dict(frontend.status.status(self.h)) + result = dict(dispatcher.status.status(self.h)) self.assert_('repeat' in result) self.assertEqual(int(result['repeat']), 0) def test_status_method_contains_repeat_is_1(self): self.b.playback.repeat = 1 - result = dict(frontend.status.status(self.h)) + result = dict(dispatcher.status.status(self.h)) self.assert_('repeat' in result) self.assertEqual(int(result['repeat']), 1) def test_status_method_contains_random_is_0(self): - result = dict(frontend.status.status(self.h)) + result = dict(dispatcher.status.status(self.h)) self.assert_('random' in result) self.assertEqual(int(result['random']), 0) def test_status_method_contains_random_is_1(self): self.b.playback.random = 1 - result = dict(frontend.status.status(self.h)) + result = dict(dispatcher.status.status(self.h)) self.assert_('random' in result) self.assertEqual(int(result['random']), 1) def test_status_method_contains_single(self): - result = dict(frontend.status.status(self.h)) + result = dict(dispatcher.status.status(self.h)) self.assert_('single' in result) self.assert_(int(result['single']) in (0, 1)) def test_status_method_contains_consume_is_0(self): - result = dict(frontend.status.status(self.h)) + result = dict(dispatcher.status.status(self.h)) self.assert_('consume' in result) self.assertEqual(int(result['consume']), 0) def test_status_method_contains_consume_is_1(self): self.b.playback.consume = 1 - result = dict(frontend.status.status(self.h)) + result = dict(dispatcher.status.status(self.h)) self.assert_('consume' in result) self.assertEqual(int(result['consume']), 1) def test_status_method_contains_playlist(self): - result = dict(frontend.status.status(self.h)) + result = dict(dispatcher.status.status(self.h)) self.assert_('playlist' in result) self.assert_(int(result['playlist']) in xrange(0, 2**31 - 1)) def test_status_method_contains_playlistlength(self): - result = dict(frontend.status.status(self.h)) + result = dict(dispatcher.status.status(self.h)) self.assert_('playlistlength' in result) self.assert_(int(result['playlistlength']) >= 0) def test_status_method_contains_xfade(self): - result = dict(frontend.status.status(self.h)) + result = dict(dispatcher.status.status(self.h)) self.assert_('xfade' in result) self.assert_(int(result['xfade']) >= 0) def test_status_method_contains_state_is_play(self): self.b.playback.state = self.b.playback.PLAYING - result = dict(frontend.status.status(self.h)) + result = dict(dispatcher.status.status(self.h)) self.assert_('state' in result) self.assertEqual(result['state'], 'play') def test_status_method_contains_state_is_stop(self): self.b.playback.state = self.b.playback.STOPPED - result = dict(frontend.status.status(self.h)) + result = dict(dispatcher.status.status(self.h)) self.assert_('state' in result) self.assertEqual(result['state'], 'stop') def test_status_method_contains_state_is_pause(self): self.b.playback.state = self.b.playback.PLAYING self.b.playback.state = self.b.playback.PAUSED - result = dict(frontend.status.status(self.h)) + result = dict(dispatcher.status.status(self.h)) self.assert_('state' in result) self.assertEqual(result['state'], 'pause') def test_status_method_when_playlist_loaded_contains_song(self): self.b.current_playlist.append([Track()]) self.b.playback.play() - result = dict(frontend.status.status(self.h)) + result = dict(dispatcher.status.status(self.h)) self.assert_('song' in result) self.assert_(int(result['song']) >= 0) def test_status_method_when_playlist_loaded_contains_cpid_as_songid(self): self.b.current_playlist.append([Track()]) self.b.playback.play() - result = dict(frontend.status.status(self.h)) + result = dict(dispatcher.status.status(self.h)) self.assert_('songid' in result) self.assertEqual(int(result['songid']), 1) def test_status_method_when_playing_contains_time_with_no_length(self): self.b.current_playlist.append([Track(length=None)]) self.b.playback.play() - result = dict(frontend.status.status(self.h)) + result = dict(dispatcher.status.status(self.h)) self.assert_('time' in result) (position, total) = result['time'].split(':') position = int(position) @@ -181,7 +181,7 @@ class StatusHandlerTest(unittest.TestCase): def test_status_method_when_playing_contains_time_with_length(self): self.b.current_playlist.append([Track(length=10000)]) self.b.playback.play() - result = dict(frontend.status.status(self.h)) + result = dict(dispatcher.status.status(self.h)) self.assert_('time' in result) (position, total) = result['time'].split(':') position = int(position) @@ -191,13 +191,13 @@ class StatusHandlerTest(unittest.TestCase): def test_status_method_when_playing_contains_elapsed(self): self.b.playback.state = self.b.playback.PAUSED self.b.playback._play_time_accumulated = 59123 - result = dict(frontend.status.status(self.h)) + result = dict(dispatcher.status.status(self.h)) self.assert_('elapsed' in result) self.assertEqual(int(result['elapsed']), 59123) def test_status_method_when_playing_contains_bitrate(self): self.b.current_playlist.append([Track(bitrate=320)]) self.b.playback.play() - result = dict(frontend.status.status(self.h)) + result = dict(dispatcher.status.status(self.h)) self.assert_('bitrate' in result) self.assertEqual(int(result['bitrate']), 320) diff --git a/tests/frontends/mpd/stickers_test.py b/tests/frontends/mpd/stickers_test.py index 401eaf57..5b66d723 100644 --- a/tests/frontends/mpd/stickers_test.py +++ b/tests/frontends/mpd/stickers_test.py @@ -1,13 +1,13 @@ import unittest from mopidy.backends.dummy import DummyBackend -from mopidy.frontends.mpd import frontend +from mopidy.frontends.mpd import dispatcher from mopidy.mixers.dummy import DummyMixer class StickersHandlerTest(unittest.TestCase): def setUp(self): self.b = DummyBackend(mixer_class=DummyMixer) - self.h = frontend.MpdFrontend(backend=self.b) + self.h = dispatcher.MpdDispatcher(backend=self.b) def test_sticker_get(self): result = self.h.handle_request( diff --git a/tests/frontends/mpd/stored_playlists_test.py b/tests/frontends/mpd/stored_playlists_test.py index 6e5717af..a24cbb88 100644 --- a/tests/frontends/mpd/stored_playlists_test.py +++ b/tests/frontends/mpd/stored_playlists_test.py @@ -2,14 +2,14 @@ import datetime as dt import unittest from mopidy.backends.dummy import DummyBackend -from mopidy.frontends.mpd import frontend +from mopidy.frontends.mpd import dispatcher from mopidy.mixers.dummy import DummyMixer from mopidy.models import Track, Playlist class StoredPlaylistsHandlerTest(unittest.TestCase): def setUp(self): self.b = DummyBackend(mixer_class=DummyMixer) - self.h = frontend.MpdFrontend(backend=self.b) + self.h = dispatcher.MpdDispatcher(backend=self.b) def test_listplaylist(self): self.b.stored_playlists.playlists = [ From 5e10ad0e05c617f5b90ce64cc1cfe0677d30d119 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 20 Aug 2010 00:38:36 +0200 Subject: [PATCH 02/23] Replace SERVER and FRONTEND with a new FRONTENDS setting --- docs/api/mpd.rst | 19 +++++++++++++++--- docs/changes.rst | 3 +++ mopidy/__main__.py | 6 +++--- mopidy/frontends/mpd/__init__.py | 33 ++++++++++++++++++++++++++++++++ mopidy/frontends/mpd/session.py | 4 ++-- mopidy/process.py | 11 +++++------ mopidy/settings.py | 16 ++++++---------- mopidy/utils/settings.py | 4 +++- 8 files changed, 71 insertions(+), 25 deletions(-) diff --git a/docs/api/mpd.rst b/docs/api/mpd.rst index 7bf7fe7b..6361e909 100644 --- a/docs/api/mpd.rst +++ b/docs/api/mpd.rst @@ -4,6 +4,8 @@ .. automodule:: mopidy.frontends.mpd :synopsis: MPD frontend + :members: + :undoc-members: MPD server @@ -17,10 +19,21 @@ MPD server .. inheritance-diagram:: mopidy.frontends.mpd.server -MPD frontend -============ +MPD session +=========== -.. automodule:: mopidy.frontends.mpd.frontend +.. automodule:: mopidy.frontends.mpd.session + :synopsis: MPD client session + :members: + :undoc-members: + +.. inheritance-diagram:: mopidy.frontends.mpd.session + + +MPD dispatcher +============== + +.. automodule:: mopidy.frontends.mpd.dispatcher :synopsis: MPD request dispatcher :members: :undoc-members: diff --git a/docs/changes.rst b/docs/changes.rst index 341ef850..7c2ce19d 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -58,6 +58,9 @@ greatly improved MPD client support. - Support for single track repeat added. (Fixes: :issue:`4`) - Rename ``mopidy.frontends.mpd.{serializer => translator}`` to match naming in backends. + - Remove setting :attr:`mopidy.settings.SERVER` and + :attr:`mopidy.settings.FRONTEND` in favour of the new + :attr:`mopidy.settings.FRONTENDS`. - Backends: diff --git a/mopidy/__main__.py b/mopidy/__main__.py index a2230180..1c0318e7 100644 --- a/mopidy/__main__.py +++ b/mopidy/__main__.py @@ -24,11 +24,11 @@ def main(): logger.info('-- Starting Mopidy --') get_or_create_folder('~/.mopidy/') core_queue = multiprocessing.Queue() - get_class(settings.SERVER)(core_queue).start() output_class = get_class(settings.OUTPUT) backend_class = get_class(settings.BACKENDS[0]) - frontend_class = get_class(settings.FRONTEND) - core = CoreProcess(core_queue, output_class, backend_class, frontend_class) + frontend = get_class(settings.FRONTENDS[0])() + frontend.start_server(core_queue) + core = CoreProcess(core_queue, output_class, backend_class, frontend) core.start() asyncore.loop() diff --git a/mopidy/frontends/mpd/__init__.py b/mopidy/frontends/mpd/__init__.py index e69de29b..53f2003f 100644 --- a/mopidy/frontends/mpd/__init__.py +++ b/mopidy/frontends/mpd/__init__.py @@ -0,0 +1,33 @@ +from mopidy.frontends.mpd.dispatcher import MpdDispatcher +from mopidy.frontends.mpd.server import MpdServer + +class MpdFrontend(object): + """ + The MPD frontend. + """ + + def __init__(self): + self.server = None + self.dispatcher = None + + def start_server(self, core_queue): + """ + Starts the MPD server. + + :param core_queue: the core queue + :type core_queue: :class:`multiprocessing.Queue` + """ + self.server = MpdServer(core_queue) + self.server.start() + + def create_dispatcher(self, backend): + """ + Creates a dispatcher for MPD requests. + + :param backend: the backend + :type backend: :class:`mopidy.backends.base.BaseBackend` + :rtype: :class:`mopidy.frontends.mpd.dispatcher.MpdDispatcher` + """ + + self.dispatcher = MpdDispatcher(backend) + return self.dispatcher diff --git a/mopidy/frontends/mpd/session.py b/mopidy/frontends/mpd/session.py index 0a7533e7..305c8b84 100644 --- a/mopidy/frontends/mpd/session.py +++ b/mopidy/frontends/mpd/session.py @@ -11,8 +11,8 @@ logger = logging.getLogger('mopidy.frontends.mpd.session') class MpdSession(asynchat.async_chat): """ - The MPD client session. Keeps track of a single client and dispatches its - MPD requests to the frontend. + The MPD client session. Keeps track of a single client and passes its + MPD requests to the dispatcher. """ def __init__(self, server, client_socket, client_socket_address, diff --git a/mopidy/process.py b/mopidy/process.py index 01ac8ed4..5fac3d65 100644 --- a/mopidy/process.py +++ b/mopidy/process.py @@ -36,17 +36,16 @@ class BaseProcess(multiprocessing.Process): class CoreProcess(BaseProcess): - def __init__(self, core_queue, output_class, backend_class, - frontend_class): + def __init__(self, core_queue, output_class, backend_class, frontend): super(CoreProcess, self).__init__() self.core_queue = core_queue self.output_queue = None self.output_class = output_class self.backend_class = backend_class - self.frontend_class = frontend_class self.output = None self.backend = None - self.frontend = None + self.frontend = frontend + self.dispatcher = None def run_inside_try(self): self.setup() @@ -58,13 +57,13 @@ class CoreProcess(BaseProcess): self.output_queue = multiprocessing.Queue() self.output = self.output_class(self.core_queue, self.output_queue) self.backend = self.backend_class(self.core_queue, self.output_queue) - self.frontend = self.frontend_class(self.backend) + self.dispatcher = self.frontend.create_dispatcher(self.backend) def process_message(self, message): if message.get('to') == 'output': self.output_queue.put(message) elif message['command'] == 'mpd_request': - response = self.frontend.handle_request(message['request']) + response = self.dispatcher.handle_request(message['request']) connection = unpickle_connection(message['reply_to']) connection.send(response) elif message['command'] == 'end_of_track': diff --git a/mopidy/settings.py b/mopidy/settings.py index 895a1e24..8a0ae862 100644 --- a/mopidy/settings.py +++ b/mopidy/settings.py @@ -41,12 +41,15 @@ DUMP_LOG_FORMAT = CONSOLE_LOG_FORMAT #: DUMP_LOG_FILENAME = u'dump.log' DUMP_LOG_FILENAME = u'dump.log' -#: Protocol frontend to use. +#: List of server frontends to use. #: #: Default:: #: -#: FRONTEND = u'mopidy.frontends.mpd.dispatcher.MpdDispatcher' -FRONTEND = u'mopidy.frontends.mpd.dispatcher.MpdDispatcher' +#: FRONTENDS = (u'mopidy.frontends.mpd.MpdFrontend',) +#: +#: .. note:: +#: Currently only the first frontend in the list is used. +FRONTENDS = (u'mopidy.frontends.mpd.MpdFrontend',) #: Path to folder with local music. #: @@ -127,13 +130,6 @@ MIXER_MAX_VOLUME = 100 #: OUTPUT = u'mopidy.outputs.gstreamer.GStreamerOutput' OUTPUT = u'mopidy.outputs.gstreamer.GStreamerOutput' -#: Server to use. -#: -#: Default:: -#: -#: SERVER = u'mopidy.frontends.mpd.server.MpdServer' -SERVER = u'mopidy.frontends.mpd.server.MpdServer' - #: Which address Mopidy's MPD server should bind to. #: #:Examples: diff --git a/mopidy/utils/settings.py b/mopidy/utils/settings.py index 478a03e6..f1d213de 100644 --- a/mopidy/utils/settings.py +++ b/mopidy/utils/settings.py @@ -37,7 +37,7 @@ class SettingsProxy(object): def current(self): current = copy(self.default) current.update(self.local) - return current + return current def __getattr__(self, attr): if not self._is_setting(attr): @@ -81,6 +81,8 @@ def validate_settings(defaults, settings): errors = {} changed = { + 'FRONTEND': 'FRONTENDS', + 'SERVER': None, 'SERVER_HOSTNAME': 'MPD_SERVER_HOSTNAME', 'SERVER_PORT': 'MPD_SERVER_PORT', 'SPOTIFY_LIB_APPKEY': None, From 222982e444de47082be763e3f7ab2bcf5442183c Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 20 Aug 2010 00:44:09 +0200 Subject: [PATCH 03/23] Move get_mpd_protocol_version() into MPD frontend --- mopidy/__init__.py | 3 --- mopidy/frontends/mpd/protocol/__init__.py | 3 +++ mopidy/frontends/mpd/session.py | 5 ++--- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/mopidy/__init__.py b/mopidy/__init__.py index e3321041..7cdcad6a 100644 --- a/mopidy/__init__.py +++ b/mopidy/__init__.py @@ -5,9 +5,6 @@ if not (2, 6) <= sys.version_info < (3,): def get_version(): return u'0.1.0a4' -def get_mpd_protocol_version(): - return u'0.16.0' - class MopidyException(Exception): def __init__(self, message, *args, **kwargs): super(MopidyException, self).__init__(message, *args, **kwargs) diff --git a/mopidy/frontends/mpd/protocol/__init__.py b/mopidy/frontends/mpd/protocol/__init__.py index c27bf0de..756aa3c3 100644 --- a/mopidy/frontends/mpd/protocol/__init__.py +++ b/mopidy/frontends/mpd/protocol/__init__.py @@ -18,6 +18,9 @@ ENCODING = u'utf-8' #: The MPD protocol uses ``\n`` as line terminator. LINE_TERMINATOR = u'\n' +#: The MPD protocol version is 0.16.0. +VERSION = u'0.16.0' + mpd_commands = set() request_handlers = {} diff --git a/mopidy/frontends/mpd/session.py b/mopidy/frontends/mpd/session.py index 305c8b84..e5d2d12d 100644 --- a/mopidy/frontends/mpd/session.py +++ b/mopidy/frontends/mpd/session.py @@ -2,10 +2,9 @@ import asynchat import logging import multiprocessing -from mopidy import get_mpd_protocol_version +from mopidy.frontends.mpd.protocol import ENCODING, LINE_TERMINATOR, VERSION from mopidy.process import pickle_connection from mopidy.utils import indent -from .protocol import ENCODING, LINE_TERMINATOR logger = logging.getLogger('mopidy.frontends.mpd.session') @@ -27,7 +26,7 @@ class MpdSession(asynchat.async_chat): def start(self): """Start a new client session.""" - self.send_response(u'OK MPD %s' % get_mpd_protocol_version()) + self.send_response(u'OK MPD %s' % VERSION) def collect_incoming_data(self, data): """Collect incoming data into buffer until a terminator is found.""" From ce878ca79785f8edceb1429fa0a64a3addd9b622 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 20 Aug 2010 00:51:01 +0200 Subject: [PATCH 04/23] Turn on last modified time stamps in Sphinx as the docs now are only built conditionally --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index c95c39df..d0d8f3af 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -130,7 +130,7 @@ html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' +html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. From aede9762f8d442d502552d389bb46a3acb3d2272 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 20 Aug 2010 01:07:23 +0200 Subject: [PATCH 05/23] Split BaseProcess snd CoreProcess into two files --- mopidy/__main__.py | 2 +- mopidy/backends/libspotify/playback.py | 2 +- mopidy/{process.py => core.py} | 37 ++----------------------- mopidy/frontends/mpd/session.py | 2 +- mopidy/mixers/gstreamer_software.py | 2 +- mopidy/mixers/nad.py | 2 +- mopidy/outputs/gstreamer.py | 2 +- mopidy/utils/process.py | 38 ++++++++++++++++++++++++++ 8 files changed, 46 insertions(+), 41 deletions(-) rename mopidy/{process.py => core.py} (63%) create mode 100644 mopidy/utils/process.py diff --git a/mopidy/__main__.py b/mopidy/__main__.py index 1c0318e7..ac090f16 100644 --- a/mopidy/__main__.py +++ b/mopidy/__main__.py @@ -10,7 +10,7 @@ sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../'))) from mopidy import get_version, settings, SettingsError -from mopidy.process import CoreProcess +from mopidy.core import CoreProcess from mopidy.utils import get_class from mopidy.utils.path import get_or_create_folder from mopidy.utils.settings import list_settings_optparse_callback diff --git a/mopidy/backends/libspotify/playback.py b/mopidy/backends/libspotify/playback.py index 1195e9bc..ed5ba697 100644 --- a/mopidy/backends/libspotify/playback.py +++ b/mopidy/backends/libspotify/playback.py @@ -4,7 +4,7 @@ import multiprocessing from spotify import Link, SpotifyError from mopidy.backends.base import BasePlaybackController -from mopidy.process import pickle_connection +from mopidy.utils.process import pickle_connection logger = logging.getLogger('mopidy.backends.libspotify.playback') diff --git a/mopidy/process.py b/mopidy/core.py similarity index 63% rename from mopidy/process.py rename to mopidy/core.py index 008129b3..06e87294 100644 --- a/mopidy/process.py +++ b/mopidy/core.py @@ -1,42 +1,9 @@ import logging import multiprocessing -from multiprocessing.reduction import reduce_connection -import pickle -import sys -from mopidy import SettingsError - -logger = logging.getLogger('mopidy.process') - -def pickle_connection(connection): - return pickle.dumps(reduce_connection(connection)) - -def unpickle_connection(pickled_connection): - # From http://stackoverflow.com/questions/1446004 - (func, args) = pickle.loads(pickled_connection) - return func(*args) - - -class BaseProcess(multiprocessing.Process): - def run(self): - try: - self.run_inside_try() - except KeyboardInterrupt: - logger.info(u'Interrupted by user') - sys.exit(0) - except SettingsError as e: - logger.error(e.message) - sys.exit(1) - except ImportError as e: - logger.error(e) - sys.exit(1) - except Exception as e: - logger.exception(e) - raise e - - def run_inside_try(self): - raise NotImplementedError +from mopidy.utils.process import BaseProcess, unpickle_connection +logger = logging.getLogger('mopidy.core') class CoreProcess(BaseProcess): def __init__(self, core_queue, output_class, backend_class, frontend): diff --git a/mopidy/frontends/mpd/session.py b/mopidy/frontends/mpd/session.py index e5d2d12d..a9eba73e 100644 --- a/mopidy/frontends/mpd/session.py +++ b/mopidy/frontends/mpd/session.py @@ -3,8 +3,8 @@ import logging import multiprocessing from mopidy.frontends.mpd.protocol import ENCODING, LINE_TERMINATOR, VERSION -from mopidy.process import pickle_connection from mopidy.utils import indent +from mopidy.utils.process import pickle_connection logger = logging.getLogger('mopidy.frontends.mpd.session') diff --git a/mopidy/mixers/gstreamer_software.py b/mopidy/mixers/gstreamer_software.py index 2910ef72..1225cafd 100644 --- a/mopidy/mixers/gstreamer_software.py +++ b/mopidy/mixers/gstreamer_software.py @@ -1,7 +1,7 @@ import multiprocessing from mopidy.mixers import BaseMixer -from mopidy.process import pickle_connection +from mopidy.utils.process import pickle_connection class GStreamerSoftwareMixer(BaseMixer): """Mixer which uses GStreamer to control volume in software.""" diff --git a/mopidy/mixers/nad.py b/mopidy/mixers/nad.py index d78863aa..b8a4f41a 100644 --- a/mopidy/mixers/nad.py +++ b/mopidy/mixers/nad.py @@ -3,9 +3,9 @@ from serial import Serial from multiprocessing import Pipe from mopidy.mixers import BaseMixer -from mopidy.process import BaseProcess from mopidy.settings import (MIXER_EXT_PORT, MIXER_EXT_SOURCE, MIXER_EXT_SPEAKERS_A, MIXER_EXT_SPEAKERS_B) +from mopidy.utils.process import BaseProcess logger = logging.getLogger('mopidy.mixers.nad') diff --git a/mopidy/outputs/gstreamer.py b/mopidy/outputs/gstreamer.py index ca5a98c5..815edbad 100644 --- a/mopidy/outputs/gstreamer.py +++ b/mopidy/outputs/gstreamer.py @@ -8,7 +8,7 @@ import gst import logging import threading -from mopidy.process import BaseProcess, unpickle_connection +from mopidy.utils.process import BaseProcess, unpickle_connection logger = logging.getLogger('mopidy.outputs.gstreamer') diff --git a/mopidy/utils/process.py b/mopidy/utils/process.py new file mode 100644 index 00000000..5e783b73 --- /dev/null +++ b/mopidy/utils/process.py @@ -0,0 +1,38 @@ +import logging +import multiprocessing +from multiprocessing.reduction import reduce_connection +import pickle +import sys + +from mopidy import SettingsError + +logger = logging.getLogger('mopidy.utils.process') + +def pickle_connection(connection): + return pickle.dumps(reduce_connection(connection)) + +def unpickle_connection(pickled_connection): + # From http://stackoverflow.com/questions/1446004 + (func, args) = pickle.loads(pickled_connection) + return func(*args) + + +class BaseProcess(multiprocessing.Process): + def run(self): + try: + self.run_inside_try() + except KeyboardInterrupt: + logger.info(u'Interrupted by user') + sys.exit(0) + except SettingsError as e: + logger.error(e.message) + sys.exit(1) + except ImportError as e: + logger.error(e) + sys.exit(1) + except Exception as e: + logger.exception(e) + raise e + + def run_inside_try(self): + raise NotImplementedError From d43ddab5908a543236a05860fb15658ec154aa5b Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 20 Aug 2010 01:24:33 +0200 Subject: [PATCH 06/23] Fix import in test --- tests/outputs/gstreamer_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/outputs/gstreamer_test.py b/tests/outputs/gstreamer_test.py index c063aaee..5f681f23 100644 --- a/tests/outputs/gstreamer_test.py +++ b/tests/outputs/gstreamer_test.py @@ -2,8 +2,8 @@ import multiprocessing import unittest from mopidy.outputs.gstreamer import GStreamerOutput -from mopidy.process import pickle_connection from mopidy.utils.path import path_to_uri +from mopidy.utils.process import pickle_connection from tests import data_folder, SkipTest From 88a4d64a5946250e2093e8371081384062e1b4bd Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 20 Aug 2010 01:24:56 +0200 Subject: [PATCH 07/23] Move MPD server into its own process --- docs/changes.rst | 5 +++-- mopidy/__main__.py | 3 ++- mopidy/frontends/mpd/__init__.py | 7 +++---- mopidy/frontends/mpd/process.py | 18 ++++++++++++++++++ 4 files changed, 26 insertions(+), 7 deletions(-) create mode 100644 mopidy/frontends/mpd/process.py diff --git a/docs/changes.rst b/docs/changes.rst index 7c2ce19d..4fdc8c1f 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -39,8 +39,6 @@ greatly improved MPD client support. the packages created by ``setup.py`` for i.e. PyPI. - MPD frontend: - - Relocate from :mod:`mopidy.mpd` to :mod:`mopidy.frontends.mpd`. - - Split gigantic protocol implementation into eleven modules. - Search improvements, including support for multi-word search. - Fixed ``play "-1"`` and ``playid "-1"`` behaviour when playlist is empty or when a current track is set. @@ -56,11 +54,14 @@ greatly improved MPD client support. - Fix ``load`` so that one can append a playlist to the current playlist, and make it return the correct error message if the playlist is not found. - Support for single track repeat added. (Fixes: :issue:`4`) + - Relocate from :mod:`mopidy.mpd` to :mod:`mopidy.frontends.mpd`. + - Split gigantic protocol implementation into eleven modules. - Rename ``mopidy.frontends.mpd.{serializer => translator}`` to match naming in backends. - Remove setting :attr:`mopidy.settings.SERVER` and :attr:`mopidy.settings.FRONTEND` in favour of the new :attr:`mopidy.settings.FRONTENDS`. + - Run MPD server in its own process. - Backends: diff --git a/mopidy/__main__.py b/mopidy/__main__.py index ac090f16..247c68b3 100644 --- a/mopidy/__main__.py +++ b/mopidy/__main__.py @@ -30,7 +30,8 @@ def main(): frontend.start_server(core_queue) core = CoreProcess(core_queue, output_class, backend_class, frontend) core.start() - asyncore.loop() + #asyncore.loop() + logger.debug('Main done') def _parse_options(): parser = optparse.OptionParser(version='Mopidy %s' % get_version()) diff --git a/mopidy/frontends/mpd/__init__.py b/mopidy/frontends/mpd/__init__.py index 53f2003f..048f5748 100644 --- a/mopidy/frontends/mpd/__init__.py +++ b/mopidy/frontends/mpd/__init__.py @@ -1,5 +1,5 @@ from mopidy.frontends.mpd.dispatcher import MpdDispatcher -from mopidy.frontends.mpd.server import MpdServer +from mopidy.frontends.mpd.process import MpdProcess class MpdFrontend(object): """ @@ -17,8 +17,8 @@ class MpdFrontend(object): :param core_queue: the core queue :type core_queue: :class:`multiprocessing.Queue` """ - self.server = MpdServer(core_queue) - self.server.start() + self.process = MpdProcess(core_queue) + self.process.start() def create_dispatcher(self, backend): """ @@ -28,6 +28,5 @@ class MpdFrontend(object): :type backend: :class:`mopidy.backends.base.BaseBackend` :rtype: :class:`mopidy.frontends.mpd.dispatcher.MpdDispatcher` """ - self.dispatcher = MpdDispatcher(backend) return self.dispatcher diff --git a/mopidy/frontends/mpd/process.py b/mopidy/frontends/mpd/process.py new file mode 100644 index 00000000..95ae855f --- /dev/null +++ b/mopidy/frontends/mpd/process.py @@ -0,0 +1,18 @@ +import asyncore +import logging + +from mopidy.frontends.mpd.server import MpdServer +from mopidy.utils.process import BaseProcess + +logger = logging.getLogger('mopidy.frontends.mpd.process') + +class MpdProcess(BaseProcess): + def __init__(self, core_queue): + super(MpdProcess, self).__init__() + self.core_queue = core_queue + + def run_inside_try(self): + logger.debug(u'Starting MPD server process') + server = MpdServer(self.core_queue) + server.start() + asyncore.loop() From 560b8be86f300789f140b34317e04228606b916c Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 20 Aug 2010 01:31:56 +0200 Subject: [PATCH 08/23] Move log setup to mopidy.utils.log --- mopidy/__main__.py | 29 ++--------------------------- mopidy/utils/log.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 27 deletions(-) create mode 100644 mopidy/utils/log.py diff --git a/mopidy/__main__.py b/mopidy/__main__.py index 247c68b3..fb22ddde 100644 --- a/mopidy/__main__.py +++ b/mopidy/__main__.py @@ -1,6 +1,4 @@ -import asyncore import logging -import logging.handlers import multiprocessing import optparse import os @@ -12,6 +10,7 @@ sys.path.insert(0, from mopidy import get_version, settings, SettingsError from mopidy.core import CoreProcess from mopidy.utils import get_class +from mopidy.utils.log import setup_logging from mopidy.utils.path import get_or_create_folder from mopidy.utils.settings import list_settings_optparse_callback @@ -19,7 +18,7 @@ logger = logging.getLogger('mopidy.main') def main(): options = _parse_options() - _setup_logging(options.verbosity_level, options.dump) + setup_logging(options.verbosity_level, options.dump) settings.validate() logger.info('-- Starting Mopidy --') get_or_create_folder('~/.mopidy/') @@ -30,7 +29,6 @@ def main(): frontend.start_server(core_queue) core = CoreProcess(core_queue, output_class, backend_class, frontend) core.start() - #asyncore.loop() logger.debug('Main done') def _parse_options(): @@ -49,29 +47,6 @@ def _parse_options(): help='list current settings') return parser.parse_args()[0] -def _setup_logging(verbosity_level, dump): - _setup_console_logging(verbosity_level) - if dump: - _setup_dump_logging() - -def _setup_console_logging(verbosity_level): - if verbosity_level == 0: - level = logging.WARNING - elif verbosity_level == 2: - level = logging.DEBUG - else: - level = logging.INFO - logging.basicConfig(format=settings.CONSOLE_LOG_FORMAT, level=level) - -def _setup_dump_logging(): - root = logging.getLogger('') - root.setLevel(logging.DEBUG) - formatter = logging.Formatter(settings.DUMP_LOG_FORMAT) - handler = logging.handlers.RotatingFileHandler( - settings.DUMP_LOG_FILENAME, maxBytes=102400, backupCount=3) - handler.setFormatter(formatter) - root.addHandler(handler) - if __name__ == '__main__': try: main() diff --git a/mopidy/utils/log.py b/mopidy/utils/log.py new file mode 100644 index 00000000..e75a6df6 --- /dev/null +++ b/mopidy/utils/log.py @@ -0,0 +1,28 @@ +import logging +import logging.handlers + +from mopidy import settings + +def setup_logging(verbosity_level, dump): + setup_console_logging(verbosity_level) + if dump: + setup_dump_logging() + +def setup_console_logging(verbosity_level): + if verbosity_level == 0: + level = logging.WARNING + elif verbosity_level == 2: + level = logging.DEBUG + else: + level = logging.INFO + logging.basicConfig(format=settings.CONSOLE_LOG_FORMAT, level=level) + +def setup_dump_logging(): + root = logging.getLogger('') + root.setLevel(logging.DEBUG) + formatter = logging.Formatter(settings.DUMP_LOG_FORMAT) + handler = logging.handlers.RotatingFileHandler( + settings.DUMP_LOG_FILENAME, maxBytes=102400, backupCount=3) + handler.setFormatter(formatter) + root.addHandler(handler) + From f73800f8e4ccd76d858c08d8cc8a72a6f2274fb6 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 20 Aug 2010 01:41:54 +0200 Subject: [PATCH 09/23] Validate settings a tad later --- mopidy/__main__.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/mopidy/__main__.py b/mopidy/__main__.py index fb22ddde..a0a91fca 100644 --- a/mopidy/__main__.py +++ b/mopidy/__main__.py @@ -17,11 +17,14 @@ from mopidy.utils.settings import list_settings_optparse_callback logger = logging.getLogger('mopidy.main') def main(): - options = _parse_options() + options = parse_options() setup_logging(options.verbosity_level, options.dump) - settings.validate() + logger.info('-- Starting Mopidy --') + get_or_create_folder('~/.mopidy/') + settings.validate() + core_queue = multiprocessing.Queue() output_class = get_class(settings.OUTPUT) backend_class = get_class(settings.BACKENDS[0]) @@ -29,9 +32,10 @@ def main(): frontend.start_server(core_queue) core = CoreProcess(core_queue, output_class, backend_class, frontend) core.start() + logger.debug('Main done') -def _parse_options(): +def parse_options(): parser = optparse.OptionParser(version='Mopidy %s' % get_version()) parser.add_option('-q', '--quiet', action='store_const', const=0, dest='verbosity_level', From 60bca18b467ab5e97217c39223d835365c78188a Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 20 Aug 2010 01:52:20 +0200 Subject: [PATCH 10/23] Run CoreProcess in the main process --- mopidy/__main__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mopidy/__main__.py b/mopidy/__main__.py index a0a91fca..e56b82ab 100644 --- a/mopidy/__main__.py +++ b/mopidy/__main__.py @@ -31,9 +31,9 @@ def main(): frontend = get_class(settings.FRONTENDS[0])() frontend.start_server(core_queue) core = CoreProcess(core_queue, output_class, backend_class, frontend) - core.start() - logger.debug('Main done') + # Explictly call run instead of start, so it runs in this process + core.run() def parse_options(): parser = optparse.OptionParser(version='Mopidy %s' % get_version()) From ea699eb121c5c1dad32fd4246355e2d2ee1de9aa Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 20 Aug 2010 01:59:14 +0200 Subject: [PATCH 11/23] Name all processes --- mopidy/core.py | 2 +- mopidy/frontends/mpd/process.py | 2 +- mopidy/mixers/nad.py | 2 +- mopidy/outputs/gstreamer.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/mopidy/core.py b/mopidy/core.py index 06e87294..615fde26 100644 --- a/mopidy/core.py +++ b/mopidy/core.py @@ -7,7 +7,7 @@ logger = logging.getLogger('mopidy.core') class CoreProcess(BaseProcess): def __init__(self, core_queue, output_class, backend_class, frontend): - super(CoreProcess, self).__init__() + super(CoreProcess, self).__init__(name='CoreProcess') self.core_queue = core_queue self.output_queue = None self.output_class = output_class diff --git a/mopidy/frontends/mpd/process.py b/mopidy/frontends/mpd/process.py index 95ae855f..7bd95900 100644 --- a/mopidy/frontends/mpd/process.py +++ b/mopidy/frontends/mpd/process.py @@ -8,7 +8,7 @@ logger = logging.getLogger('mopidy.frontends.mpd.process') class MpdProcess(BaseProcess): def __init__(self, core_queue): - super(MpdProcess, self).__init__() + super(MpdProcess, self).__init__(name='MpdProcess') self.core_queue = core_queue def run_inside_try(self): diff --git a/mopidy/mixers/nad.py b/mopidy/mixers/nad.py index b8a4f41a..f859791b 100644 --- a/mopidy/mixers/nad.py +++ b/mopidy/mixers/nad.py @@ -74,7 +74,7 @@ class NadTalker(BaseProcess): _nad_volume = None def __init__(self, pipe=None): - super(NadTalker, self).__init__() + super(NadTalker, self).__init__(name='NadTalker') self.pipe = pipe self._device = None diff --git a/mopidy/outputs/gstreamer.py b/mopidy/outputs/gstreamer.py index 815edbad..a1544f87 100644 --- a/mopidy/outputs/gstreamer.py +++ b/mopidy/outputs/gstreamer.py @@ -49,7 +49,7 @@ class GStreamerProcess(BaseProcess): ]) def __init__(self, core_queue, output_queue): - super(GStreamerProcess, self).__init__() + super(GStreamerProcess, self).__init__(name='GStreamerProcess') self.core_queue = core_queue self.output_queue = output_queue self.gst_pipeline = None From 51b5910e685874e42b32a4399878774fb3afb6bb Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 20 Aug 2010 01:59:27 +0200 Subject: [PATCH 12/23] Add process name to BaseProcess logging --- mopidy/utils/process.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mopidy/utils/process.py b/mopidy/utils/process.py index 5e783b73..73224840 100644 --- a/mopidy/utils/process.py +++ b/mopidy/utils/process.py @@ -19,10 +19,11 @@ def unpickle_connection(pickled_connection): class BaseProcess(multiprocessing.Process): def run(self): + logger.debug(u'%s: Starting process', self.name) try: self.run_inside_try() except KeyboardInterrupt: - logger.info(u'Interrupted by user') + logger.info(u'%s: Interrupted by user', self.name) sys.exit(0) except SettingsError as e: logger.error(e.message) From a67aaef8c73ee3a7c4c0ea36b344ea952576d3a7 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 20 Aug 2010 03:08:59 +0200 Subject: [PATCH 13/23] Update class instantiation/usage graph --- docs/development/internals.rst | 99 ++++++++++++++++++++++++++-------- 1 file changed, 76 insertions(+), 23 deletions(-) diff --git a/docs/development/internals.rst b/docs/development/internals.rst index 085b55ac..73dc59e6 100644 --- a/docs/development/internals.rst +++ b/docs/development/internals.rst @@ -12,40 +12,93 @@ In addition to what you'll find here, don't forget the :doc:`/api/index`. Class instantiation and usage ============================= -The following diagram shows how Mopidy with the despotify backend and ALSA -mixer is wired together. The gray nodes are part of external dependencies, and -not Mopidy. The red nodes lives in the ``main`` process (running an -:mod:`asyncore` loop), while the blue nodes lives in a secondary process named -``core`` (running a service loop in :class:`mopidy.core.CoreProcess`). +The following diagram shows how Mopidy is wired together with the MPD client, +the Spotify service, and the speakers. + +**Legend** + +- Filled red boxes are the key external systems. +- Gray boxes are external dependencies. +- Blue circles lives in the ``main`` process, also known as ``CoreProcess``. + It processing messages on the core queue. +- Purple circles lives in a process named ``MpdProcess``, running an + :mod:`asyncore` loop. +- Green circles lives in a process named ``GStreamerProcess``. +- Brown circle is a thread living in the ``CoreProcess``. .. digraph:: class_instantiation_and_usage - "spytify" [ color="gray" ] - "despotify" [ color="gray" ] - "alsaaudio" [ color="gray" ] - "__main__" [ color="red" ] + "main" [ color="blue" ] "CoreProcess" [ color="blue" ] - "DespotifyBackend" [ color="blue" ] - "AlsaMixer" [ color="blue" ] + + # Frontend + "MPD client" [ color="red", style="filled", shape="box" ] "MpdFrontend" [ color="blue" ] - "MpdServer" [ color="red" ] - "MpdSession" [ color="red" ] - "__main__" -> "CoreProcess" [ label="create" ] - "__main__" -> "MpdServer" [ label="create" ] - "CoreProcess" -> "DespotifyBackend" [ label="create" ] + "MpdProcess" [ color="purple" ] + "MpdServer" [ color="purple" ] + "MpdSession" [ color="purple" ] + "MpdDispatcher" [ color="blue" ] + + # Backend + "Libspotify\nBackend" [ color="blue" ] + "Libspotify\nSessionManager" [ color="brown" ] + "pyspotify" [ color="gray", shape="box" ] + "libspotify" [ color="gray", shape="box" ] + "Spotify" [ color="red", style="filled", shape="box" ] + + # Output/mixer + "GStreamer\nOutput" [ color="blue" ] + "GStreamer\nSoftwareMixer" [ color="blue" ] + "GStreamer\nProcess" [ color="green" ] + "GStreamer" [ color="gray", shape="box" ] + "Speakers" [ color="red", style="filled", shape="box" ] + + "main" -> "CoreProcess" [ label="create" ] + + # Frontend "CoreProcess" -> "MpdFrontend" [ label="create" ] - "MpdServer" -> "MpdSession" [ label="create one per client" ] - "MpdSession" -> "MpdFrontend" [ label="pass MPD requests to" ] - "MpdFrontend" -> "DespotifyBackend" [ label="use backend API" ] - "DespotifyBackend" -> "AlsaMixer" [ label="create and use mixer API" ] - "DespotifyBackend" -> "spytify" [ label="use Python wrapper" ] - "spytify" -> "despotify" [ label="use C library" ] - "AlsaMixer" -> "alsaaudio" [ label="use Python library" ] + "MpdFrontend" -> "MpdProcess" [ label="create" ] + "MpdProcess" -> "MpdServer" [ label="create" ] + "MpdServer" -> "MpdSession" [ label="create one\nper client" ] + "MpdSession" -> "MpdDispatcher" [ + label="pass requests\nvia core_queue" ] + "MpdDispatcher" -> "MpdSession" [ + label="pass response\nvia reply_to pipe" ] + "MpdDispatcher" -> "Libspotify\nBackend" [ label="use backend API" ] + "MPD client" -> "MpdServer" [ label="connect" ] + "MPD client" -> "MpdSession" [ label="request" ] + "MpdSession" -> "MPD client" [ label="response" ] + + # Backend + "CoreProcess" -> "Libspotify\nBackend" [ label="create" ] + "Libspotify\nBackend" -> "Libspotify\nSessionManager" [ + label="creates and uses" ] + "Libspotify\nSessionManager" -> "Libspotify\nBackend" [ + label="pass commands\nvia core_queue" ] + "Libspotify\nSessionManager" -> "pyspotify" [ label="use Python\nwrapper" ] + "pyspotify" -> "Libspotify\nSessionManager" [ label="use callbacks" ] + "pyspotify" -> "libspotify" [ label="use C library" ] + "libspotify" -> "Spotify" [ label="use service" ] + "Libspotify\nSessionManager" -> "GStreamer\nProcess" [ + label="pass commands\nand audio data\nvia output_queue" ] + + # Output/mixer + "Libspotify\nBackend" -> "GStreamer\nSoftwareMixer" [ + label="create and\nuse mixer API" ] + "GStreamer\nSoftwareMixer" -> "GStreamer\nProcess" [ + label="pass commands\nvia output_queue" ] + "CoreProcess" -> "GStreamer\nOutput" [ label="create" ] + "GStreamer\nOutput" -> "GStreamer\nProcess" [ label="create" ] + "GStreamer\nProcess" -> "GStreamer" [ label="use library" ] + "GStreamer" -> "Speakers" [ label="play audio" ] Thread/process communication ============================ +.. warning:: + This section is currently outdated. + - Everything starts with ``Main``. - ``Main`` creates a ``Core`` process which runs the frontend, backend, and mixer. From 7f95a3b0ba58bfb919636169a991eb17e1668b7f Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 20 Aug 2010 03:27:20 +0200 Subject: [PATCH 14/23] Move initialization from main to core --- mopidy/__main__.py | 11 +---------- mopidy/core.py | 29 ++++++++++++++++++++--------- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/mopidy/__main__.py b/mopidy/__main__.py index e56b82ab..86a4d5c4 100644 --- a/mopidy/__main__.py +++ b/mopidy/__main__.py @@ -1,5 +1,4 @@ import logging -import multiprocessing import optparse import os import sys @@ -9,7 +8,6 @@ sys.path.insert(0, from mopidy import get_version, settings, SettingsError from mopidy.core import CoreProcess -from mopidy.utils import get_class from mopidy.utils.log import setup_logging from mopidy.utils.path import get_or_create_folder from mopidy.utils.settings import list_settings_optparse_callback @@ -25,15 +23,8 @@ def main(): get_or_create_folder('~/.mopidy/') settings.validate() - core_queue = multiprocessing.Queue() - output_class = get_class(settings.OUTPUT) - backend_class = get_class(settings.BACKENDS[0]) - frontend = get_class(settings.FRONTENDS[0])() - frontend.start_server(core_queue) - core = CoreProcess(core_queue, output_class, backend_class, frontend) - # Explictly call run instead of start, so it runs in this process - core.run() + CoreProcess(options).run() def parse_options(): parser = optparse.OptionParser(version='Mopidy %s' % get_version()) diff --git a/mopidy/core.py b/mopidy/core.py index 615fde26..0d60b602 100644 --- a/mopidy/core.py +++ b/mopidy/core.py @@ -1,20 +1,19 @@ import logging import multiprocessing +from mopidy import settings +from mopidy.utils import get_class from mopidy.utils.process import BaseProcess, unpickle_connection logger = logging.getLogger('mopidy.core') class CoreProcess(BaseProcess): - def __init__(self, core_queue, output_class, backend_class, frontend): + def __init__(self, options): super(CoreProcess, self).__init__(name='CoreProcess') - self.core_queue = core_queue + self.options = options + self.core_queue = multiprocessing.Queue() self.output_queue = None - self.output_class = output_class - self.backend_class = backend_class - self.output = None self.backend = None - self.frontend = frontend self.dispatcher = None def run_inside_try(self): @@ -24,10 +23,22 @@ class CoreProcess(BaseProcess): self.process_message(message) def setup(self): + self.setup_output() + self.setup_backend() + self.setup_frontend() + + def setup_output(self): self.output_queue = multiprocessing.Queue() - self.output = self.output_class(self.core_queue, self.output_queue) - self.backend = self.backend_class(self.core_queue, self.output_queue) - self.dispatcher = self.frontend.create_dispatcher(self.backend) + get_class(settings.OUTPUT)(self.core_queue, self.output_queue) + + def setup_backend(self): + self.backend = get_class(settings.BACKENDS[0])( + self.core_queue, self.output_queue) + + def setup_frontend(self): + frontend = get_class(settings.FRONTENDS[0])() + frontend.start_server(self.core_queue) + self.dispatcher = frontend.create_dispatcher(self.backend) def process_message(self, message): if message.get('to') == 'output': From 681ac05561108a6c7c9cb63ed4bc5d6b8ef4d4ce Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 20 Aug 2010 10:19:32 +0200 Subject: [PATCH 15/23] Graph: MpdFrontend creates MpdDispatcher --- docs/development/internals.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/development/internals.rst b/docs/development/internals.rst index 73dc59e6..0af13aa8 100644 --- a/docs/development/internals.rst +++ b/docs/development/internals.rst @@ -58,6 +58,7 @@ the Spotify service, and the speakers. # Frontend "CoreProcess" -> "MpdFrontend" [ label="create" ] "MpdFrontend" -> "MpdProcess" [ label="create" ] + "MpdFrontend" -> "MpdDispatcher" [ label="create" ] "MpdProcess" -> "MpdServer" [ label="create" ] "MpdServer" -> "MpdSession" [ label="create one\nper client" ] "MpdSession" -> "MpdDispatcher" [ From aba0cc3ef36fe191bd53bf8695ffe09ef3269ea4 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 20 Aug 2010 12:34:13 +0200 Subject: [PATCH 16/23] Remove redundant error handling, as all these cases are handled in BaseProcess --- mopidy/__main__.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/mopidy/__main__.py b/mopidy/__main__.py index 86a4d5c4..02bdb4e5 100644 --- a/mopidy/__main__.py +++ b/mopidy/__main__.py @@ -43,14 +43,4 @@ def parse_options(): return parser.parse_args()[0] if __name__ == '__main__': - try: - main() - except KeyboardInterrupt: - logger.info(u'Interrupted by user') - sys.exit(0) - except SettingsError, e: - logger.error(e) - sys.exit(1) - except SystemExit, e: - logger.error(e) - sys.exit(1) + main() From 461830924a25641ac54834b582217391f9b2fef2 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 20 Aug 2010 12:34:41 +0200 Subject: [PATCH 17/23] libspotify thread should not stop Mopidy from exiting, and is thus a daemon thread --- mopidy/backends/libspotify/session_manager.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mopidy/backends/libspotify/session_manager.py b/mopidy/backends/libspotify/session_manager.py index 707423aa..9ea8386b 100644 --- a/mopidy/backends/libspotify/session_manager.py +++ b/mopidy/backends/libspotify/session_manager.py @@ -19,6 +19,9 @@ class LibspotifySessionManager(SpotifySessionManager, threading.Thread): def __init__(self, username, password, core_queue, output_queue): SpotifySessionManager.__init__(self, username, password) threading.Thread.__init__(self) + # Run as a daemon thread, so Mopidy won't wait for this thread to exit + # before Mopidy exits. + self.daemon = True self.core_queue = core_queue self.output_queue = output_queue self.connected = threading.Event() From 5ea3e40594ee0b222ed131d1340f743912ef75c5 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 20 Aug 2010 12:38:33 +0200 Subject: [PATCH 18/23] Rename Thread-1 => LibspotifySessionManagerThread --- mopidy/backends/libspotify/session_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mopidy/backends/libspotify/session_manager.py b/mopidy/backends/libspotify/session_manager.py index 9ea8386b..22cbb0a0 100644 --- a/mopidy/backends/libspotify/session_manager.py +++ b/mopidy/backends/libspotify/session_manager.py @@ -18,7 +18,7 @@ class LibspotifySessionManager(SpotifySessionManager, threading.Thread): def __init__(self, username, password, core_queue, output_queue): SpotifySessionManager.__init__(self, username, password) - threading.Thread.__init__(self) + threading.Thread.__init__(self, name='LibspotifySessionManagerThread') # Run as a daemon thread, so Mopidy won't wait for this thread to exit # before Mopidy exits. self.daemon = True From 79729e653b838740ff543fa3de667e4078cf6fc0 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 20 Aug 2010 12:54:18 +0200 Subject: [PATCH 19/23] Make CoreProcess.setup() more functional to show dependencies explicitly --- mopidy/__main__.py | 2 +- mopidy/core.py | 32 ++++++++++++++++---------------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/mopidy/__main__.py b/mopidy/__main__.py index 02bdb4e5..044e6ec6 100644 --- a/mopidy/__main__.py +++ b/mopidy/__main__.py @@ -24,7 +24,7 @@ def main(): settings.validate() # Explictly call run instead of start, so it runs in this process - CoreProcess(options).run() + CoreProcess().run() def parse_options(): parser = optparse.OptionParser(version='Mopidy %s' % get_version()) diff --git a/mopidy/core.py b/mopidy/core.py index 0d60b602..b45a9b96 100644 --- a/mopidy/core.py +++ b/mopidy/core.py @@ -8,13 +8,12 @@ from mopidy.utils.process import BaseProcess, unpickle_connection logger = logging.getLogger('mopidy.core') class CoreProcess(BaseProcess): - def __init__(self, options): + def __init__(self): super(CoreProcess, self).__init__(name='CoreProcess') - self.options = options self.core_queue = multiprocessing.Queue() self.output_queue = None self.backend = None - self.dispatcher = None + self.frontend = None def run_inside_try(self): self.setup() @@ -23,28 +22,29 @@ class CoreProcess(BaseProcess): self.process_message(message) def setup(self): - self.setup_output() - self.setup_backend() - self.setup_frontend() + self.output_queue = self.setup_output(self.core_queue) + self.backend = self.setup_backend(self.core_queue, self.output_queue) + self.frontend = self.setup_frontend(self.core_queue, self.backend) - def setup_output(self): - self.output_queue = multiprocessing.Queue() - get_class(settings.OUTPUT)(self.core_queue, self.output_queue) + def setup_output(self, core_queue): + output_queue = multiprocessing.Queue() + get_class(settings.OUTPUT)(core_queue, output_queue) + return output_queue - def setup_backend(self): - self.backend = get_class(settings.BACKENDS[0])( - self.core_queue, self.output_queue) + def setup_backend(self, core_queue, output_queue): + return get_class(settings.BACKENDS[0])(core_queue, output_queue) - def setup_frontend(self): + def setup_frontend(self, core_queue, backend): frontend = get_class(settings.FRONTENDS[0])() - frontend.start_server(self.core_queue) - self.dispatcher = frontend.create_dispatcher(self.backend) + frontend.start_server(core_queue) + frontend.create_dispatcher(backend) + return frontend def process_message(self, message): if message.get('to') == 'output': self.output_queue.put(message) elif message['command'] == 'mpd_request': - response = self.dispatcher.handle_request(message['request']) + response = self.frontend.dispatcher.handle_request(message['request']) connection = unpickle_connection(message['reply_to']) connection.send(response) elif message['command'] == 'end_of_track': From 976086ae65d2948c9b91e0bd6b7a3724872ffb3f Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 20 Aug 2010 16:17:22 +0200 Subject: [PATCH 20/23] Move options parsing, logging setup and settings validation into CoreProcess --- mopidy/__main__.py | 42 +++++------------------------------------- mopidy/core.py | 33 ++++++++++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 38 deletions(-) diff --git a/mopidy/__main__.py b/mopidy/__main__.py index 044e6ec6..8aee976b 100644 --- a/mopidy/__main__.py +++ b/mopidy/__main__.py @@ -1,46 +1,14 @@ -import logging -import optparse import os import sys +# Add ../ to the path so we can run Mopidy from a Git checkout without +# installing it on the system. sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../'))) -from mopidy import get_version, settings, SettingsError from mopidy.core import CoreProcess -from mopidy.utils.log import setup_logging -from mopidy.utils.path import get_or_create_folder -from mopidy.utils.settings import list_settings_optparse_callback - -logger = logging.getLogger('mopidy.main') - -def main(): - options = parse_options() - setup_logging(options.verbosity_level, options.dump) - - logger.info('-- Starting Mopidy --') - - get_or_create_folder('~/.mopidy/') - settings.validate() - - # Explictly call run instead of start, so it runs in this process - CoreProcess().run() - -def parse_options(): - parser = optparse.OptionParser(version='Mopidy %s' % get_version()) - parser.add_option('-q', '--quiet', - action='store_const', const=0, dest='verbosity_level', - help='less output (warning level)') - parser.add_option('-v', '--verbose', - action='store_const', const=2, dest='verbosity_level', - help='more output (debug level)') - parser.add_option('--dump', - action='store_true', dest='dump', - help='dump debug log to file') - parser.add_option('--list-settings', - action='callback', callback=list_settings_optparse_callback, - help='list current settings') - return parser.parse_args()[0] if __name__ == '__main__': - main() + # Explictly call run() instead of start(), since we don't need to start + # another process. + CoreProcess().run() diff --git a/mopidy/core.py b/mopidy/core.py index b45a9b96..d823bfa5 100644 --- a/mopidy/core.py +++ b/mopidy/core.py @@ -1,9 +1,13 @@ import logging import multiprocessing +import optparse -from mopidy import settings +from mopidy import get_version, settings from mopidy.utils import get_class +from mopidy.utils.log import setup_logging +from mopidy.utils.path import get_or_create_folder from mopidy.utils.process import BaseProcess, unpickle_connection +from mopidy.utils.settings import list_settings_optparse_callback logger = logging.getLogger('mopidy.core') @@ -11,21 +15,48 @@ class CoreProcess(BaseProcess): def __init__(self): super(CoreProcess, self).__init__(name='CoreProcess') self.core_queue = multiprocessing.Queue() + self.options = self.parse_options() self.output_queue = None self.backend = None self.frontend = None + def parse_options(self): + parser = optparse.OptionParser(version='Mopidy %s' % get_version()) + parser.add_option('-q', '--quiet', + action='store_const', const=0, dest='verbosity_level', + help='less output (warning level)') + parser.add_option('-v', '--verbose', + action='store_const', const=2, dest='verbosity_level', + help='more output (debug level)') + parser.add_option('--dump', + action='store_true', dest='dump', + help='dump debug log to file') + parser.add_option('--list-settings', + action='callback', callback=list_settings_optparse_callback, + help='list current settings') + return parser.parse_args()[0] + def run_inside_try(self): + logger.info(u'-- Starting Mopidy --') self.setup() while True: message = self.core_queue.get() self.process_message(message) def setup(self): + self.setup_logging() + self.setup_settings() self.output_queue = self.setup_output(self.core_queue) self.backend = self.setup_backend(self.core_queue, self.output_queue) self.frontend = self.setup_frontend(self.core_queue, self.backend) + def setup_logging(self): + setup_logging(self.options.verbosity_level, self.options.dump) + + def setup_settings(self): + get_or_create_folder('~/.mopidy/') + settings.validate() + def setup_output(self, core_queue): output_queue = multiprocessing.Queue() get_class(settings.OUTPUT)(core_queue, output_queue) From 19c4f1c09f4273661bec639cba80a090e368d7e4 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 20 Aug 2010 16:21:07 +0200 Subject: [PATCH 21/23] Readd main() method, to not break bin/mopidy --- mopidy/__main__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/mopidy/__main__.py b/mopidy/__main__.py index 8aee976b..20e78f5a 100644 --- a/mopidy/__main__.py +++ b/mopidy/__main__.py @@ -8,7 +8,10 @@ sys.path.insert(0, from mopidy.core import CoreProcess -if __name__ == '__main__': +def main(): # Explictly call run() instead of start(), since we don't need to start # another process. CoreProcess().run() + +if __name__ == '__main__': + main() From 1ae172d0c1608bd91ad27dfd06456f4568122cbc Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 20 Aug 2010 16:38:59 +0200 Subject: [PATCH 22/23] Move mopidy.utils.{ => log}.indent --- mopidy/frontends/mpd/session.py | 2 +- mopidy/utils/__init__.py | 9 --------- mopidy/utils/log.py | 8 ++++++++ mopidy/utils/settings.py | 2 +- 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/mopidy/frontends/mpd/session.py b/mopidy/frontends/mpd/session.py index a9eba73e..72a1f845 100644 --- a/mopidy/frontends/mpd/session.py +++ b/mopidy/frontends/mpd/session.py @@ -3,7 +3,7 @@ import logging import multiprocessing from mopidy.frontends.mpd.protocol import ENCODING, LINE_TERMINATOR, VERSION -from mopidy.utils import indent +from mopidy.utils.log import indent from mopidy.utils.process import pickle_connection logger = logging.getLogger('mopidy.frontends.mpd.session') diff --git a/mopidy/utils/__init__.py b/mopidy/utils/__init__.py index 277d2f3b..acbb4664 100644 --- a/mopidy/utils/__init__.py +++ b/mopidy/utils/__init__.py @@ -27,12 +27,3 @@ def get_class(name): except (ImportError, AttributeError): raise ImportError("Couldn't load: %s" % name) return class_object - -def indent(string, places=4, linebreak='\n'): - lines = string.split(linebreak) - if len(lines) == 1: - return string - result = u'' - for line in lines: - result += linebreak + ' ' * places + line - return result diff --git a/mopidy/utils/log.py b/mopidy/utils/log.py index e75a6df6..c892102a 100644 --- a/mopidy/utils/log.py +++ b/mopidy/utils/log.py @@ -26,3 +26,11 @@ def setup_dump_logging(): handler.setFormatter(formatter) root.addHandler(handler) +def indent(string, places=4, linebreak='\n'): + lines = string.split(linebreak) + if len(lines) == 1: + return string + result = u'' + for line in lines: + result += linebreak + ' ' * places + line + return result diff --git a/mopidy/utils/settings.py b/mopidy/utils/settings.py index f1d213de..e45c5521 100644 --- a/mopidy/utils/settings.py +++ b/mopidy/utils/settings.py @@ -6,7 +6,7 @@ import os import sys from mopidy import SettingsError -from mopidy.utils import indent +from mopidy.utils.log import indent logger = logging.getLogger('mopidy.utils.settings') From c2817959fea63db83a328a8ce61b95031b04c4bd Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 20 Aug 2010 16:39:10 +0200 Subject: [PATCH 23/23] Initialize the correct variable in init --- mopidy/frontends/mpd/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mopidy/frontends/mpd/__init__.py b/mopidy/frontends/mpd/__init__.py index 048f5748..02e3ab5f 100644 --- a/mopidy/frontends/mpd/__init__.py +++ b/mopidy/frontends/mpd/__init__.py @@ -7,7 +7,7 @@ class MpdFrontend(object): """ def __init__(self): - self.server = None + self.process = None self.dispatcher = None def start_server(self, core_queue):