Merge branch 'core-as-main-process' into develop

This commit is contained in:
Stein Magnus Jodal 2010-08-20 16:23:13 +02:00
commit 301344d358
15 changed files with 198 additions and 169 deletions

View File

@ -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:

View File

@ -1,85 +1,17 @@
import asyncore
import logging
import logging.handlers
import multiprocessing
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.process 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
logger = logging.getLogger('mopidy.main')
from mopidy.core import CoreProcess
def main():
options = _parse_options()
_setup_logging(options.verbosity_level, options.dump)
settings.validate()
logger.info('-- Starting Mopidy --')
get_or_create_folder('~/.mopidy/')
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)
core.start()
asyncore.loop()
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]
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)
# Explictly call run() instead of start(), since we don't need to start
# another process.
CoreProcess().run()
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()

View File

@ -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')

View File

@ -18,7 +18,10 @@ 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
self.core_queue = core_queue
self.output_queue = output_queue
self.connected = threading.Event()

88
mopidy/core.py Normal file
View File

@ -0,0 +1,88 @@
import logging
import multiprocessing
import optparse
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')
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)
return output_queue
def setup_backend(self, core_queue, output_queue):
return get_class(settings.BACKENDS[0])(core_queue, output_queue)
def setup_frontend(self, core_queue, backend):
frontend = get_class(settings.FRONTENDS[0])()
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.frontend.dispatcher.handle_request(message['request'])
connection = unpickle_connection(message['reply_to'])
connection.send(response)
elif message['command'] == 'end_of_track':
self.backend.playback.on_end_of_track()
elif message['command'] == 'stop_playback':
self.backend.playback.stop()
elif message['command'] == 'set_stored_playlists':
self.backend.stored_playlists.playlists = message['playlists']
else:
logger.warning(u'Cannot handle message: %s', message)

View File

@ -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

View File

@ -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__(name='MpdProcess')
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()

View File

@ -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')

View File

@ -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."""

View File

@ -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')
@ -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

View File

@ -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')
@ -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

View File

@ -1,79 +0,0 @@
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
class CoreProcess(BaseProcess):
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.output = None
self.backend = None
self.frontend = frontend
self.dispatcher = None
def run_inside_try(self):
self.setup()
while True:
message = self.core_queue.get()
self.process_message(message)
def setup(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)
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'])
connection = unpickle_connection(message['reply_to'])
connection.send(response)
elif message['command'] == 'end_of_track':
self.backend.playback.on_end_of_track()
elif message['command'] == 'stop_playback':
self.backend.playback.stop()
elif message['command'] == 'set_stored_playlists':
self.backend.stored_playlists.playlists = message['playlists']
else:
logger.warning(u'Cannot handle message: %s', message)

28
mopidy/utils/log.py Normal file
View File

@ -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)

39
mopidy/utils/process.py Normal file
View File

@ -0,0 +1,39 @@
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):
logger.debug(u'%s: Starting process', self.name)
try:
self.run_inside_try()
except KeyboardInterrupt:
logger.info(u'%s: Interrupted by user', self.name)
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

View File

@ -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