diff --git a/mopidy/__main__.py b/mopidy/__main__.py index a7e914a9..f0ee6085 100644 --- a/mopidy/__main__.py +++ b/mopidy/__main__.py @@ -9,6 +9,7 @@ import sys import gobject gobject.threads_init() +import pkg_resources import pykka.debug @@ -54,10 +55,11 @@ def main(): log.setup_logging(options.verbosity_level, options.save_debug_log) check_old_folders() setup_settings(options.interactive) + extensions = load_extensions() audio = setup_audio() - backends = setup_backends(audio) + backends = setup_backends(extensions, audio) core = setup_core(audio, backends) - setup_frontends(core) + setup_frontends(extensions, core) loop.run() except exceptions.SettingsError as ex: logger.error(ex.message) @@ -67,9 +69,9 @@ def main(): logger.exception(ex) finally: loop.quit() - stop_frontends() + stop_frontends(extensions) stop_core() - stop_backends() + stop_backends(extensions) stop_audio() process.stop_remaining_actors() @@ -138,51 +140,86 @@ def setup_settings(interactive): sys.exit(1) +def load_extensions(): + extensions = [] + for entry_point in pkg_resources.iter_entry_points('mopidy.extension'): + logger.debug('Loading extension %s', entry_point.name) + + try: + extension_class = entry_point.load() + except pkg_resources.DistributionNotFound as ex: + logger.info( + 'Disabled extension %s: Dependency %s not found', + entry_point.name, ex) + continue + + extension = extension_class() + + # TODO Validate configuration, filter out disabled extensions + + try: + extension.validate_environment() + except exceptions.ExtensionError as ex: + logger.info( + 'Disabled extension: %s (%s)', extension.name, ex.message) + continue + + logger.info( + 'Loaded extension %s: %s %s', + entry_point.name, extension.name, extension.version) + extensions.append(extension) + return extensions + + def setup_audio(): + logger.info('Starting Mopidy audio') return Audio.start().proxy() def stop_audio(): + logger.info('Stopping Mopidy audio') process.stop_actors_by_class(Audio) -def setup_backends(audio): +def setup_backends(extensions, audio): + logger.info('Starting Mopidy backends') backends = [] - for backend_class_name in settings.BACKENDS: - backend_class = importing.get_class(backend_class_name) - backend = backend_class.start(audio=audio).proxy() - backends.append(backend) + for extension in extensions: + for backend_class in extension.get_backend_classes(): + backend = backend_class.start(audio=audio).proxy() + backends.append(backend) return backends -def stop_backends(): - for backend_class_name in settings.BACKENDS: - process.stop_actors_by_class(importing.get_class(backend_class_name)) +def stop_backends(extensions): + logger.info('Stopping Mopidy backends') + for extension in extensions: + for backend_class in extension.get_backend_classes(): + process.stop_actors_by_class(backend_class) def setup_core(audio, backends): + logger.info('Starting Mopidy core') return Core.start(audio=audio, backends=backends).proxy() def stop_core(): + logger.info('Stopping Mopidy core') process.stop_actors_by_class(Core) -def setup_frontends(core): - for frontend_class_name in settings.FRONTENDS: - try: - importing.get_class(frontend_class_name).start(core=core) - except exceptions.OptionalDependencyError as ex: - logger.info('Disabled: %s (%s)', frontend_class_name, ex) +def setup_frontends(extensions, core): + logger.info('Starting Mopidy frontends') + for extension in extensions: + for frontend_class in extension.get_frontend_classes(): + frontend_class.start(core=core) -def stop_frontends(): - for frontend_class_name in settings.FRONTENDS: - try: - frontend_class = importing.get_class(frontend_class_name) +def stop_frontends(extensions): + logger.info('Stopping Mopidy frontends') + for extension in extensions: + for frontend_class in extension.get_frontend_classes(): process.stop_actors_by_class(frontend_class) - except exceptions.OptionalDependencyError: - pass if __name__ == '__main__': diff --git a/mopidy/backends/local/__init__.py b/mopidy/backends/local/__init__.py index 99c50e1f..c2001da5 100644 --- a/mopidy/backends/local/__init__.py +++ b/mopidy/backends/local/__init__.py @@ -27,10 +27,6 @@ https://github.com/mopidy/mopidy/issues?labels=Local+backend """ -# TODO Move import into method when BACKENDS setting is removed -from .actor import LocalBackend - - class Extension(ext.Extension): name = 'Mopidy-Local' @@ -46,4 +42,5 @@ class Extension(ext.Extension): pass def get_backend_classes(self): + from .actor import LocalBackend return [LocalBackend] diff --git a/mopidy/backends/spotify/__init__.py b/mopidy/backends/spotify/__init__.py index efa5338b..503d9eb6 100644 --- a/mopidy/backends/spotify/__init__.py +++ b/mopidy/backends/spotify/__init__.py @@ -65,10 +65,6 @@ https://github.com/mopidy/mopidy/issues?labels=Spotify+backend """ % {'config': indent(config)} -# TODO Move import into method when BACKENDS setting is removed -from .actor import SpotifyBackend - - class Extension(ext.Extension): name = 'Mopidy-Spotify' @@ -92,4 +88,5 @@ class Extension(ext.Extension): raise ExtensionError('pyspotify library not found', e) def get_backend_classes(self): + from .actor import SpotifyBackend return [SpotifyBackend] diff --git a/mopidy/backends/stream/__init__.py b/mopidy/backends/stream/__init__.py index dbf3e6d5..4096476e 100644 --- a/mopidy/backends/stream/__init__.py +++ b/mopidy/backends/stream/__init__.py @@ -24,10 +24,6 @@ https://github.com/mopidy/mopidy/issues?labels=Stream+backend """ -# TODO Move import into method when BACKENDS setting is removed -from .actor import StreamBackend - - class Extension(ext.Extension): name = 'Mopidy-Stream' @@ -43,4 +39,5 @@ class Extension(ext.Extension): pass def get_backend_classes(self): + from .actor import StreamBackend return [StreamBackend] diff --git a/mopidy/frontends/http/__init__.py b/mopidy/frontends/http/__init__.py index 25fe788f..0107e357 100644 --- a/mopidy/frontends/http/__init__.py +++ b/mopidy/frontends/http/__init__.py @@ -485,10 +485,6 @@ Example to get started with """ -# TODO Move import into method when FRONTENDS setting is removed -from .actor import HttpFrontend - - class Extension(ext.Extension): name = 'Mopidy-HTTP' @@ -512,4 +508,5 @@ class Extension(ext.Extension): raise ExtensionError('Library ws4py not found', e) def get_frontend_classes(self): + from .actor import HttpFrontend return [HttpFrontend] diff --git a/mopidy/frontends/lastfm/__init__.py b/mopidy/frontends/lastfm/__init__.py index e3f1f5c6..df41d130 100644 --- a/mopidy/frontends/lastfm/__init__.py +++ b/mopidy/frontends/lastfm/__init__.py @@ -30,10 +30,6 @@ the Last.fm frontend. """ -# TODO Move import into method when FRONTENDS setting is removed -from .actor import LastfmFrontend - - class Extension(ext.Extension): name = 'Mopidy-Lastfm' @@ -52,4 +48,5 @@ class Extension(ext.Extension): raise ExtensionError('pylast library not found', e) def get_frontend_classes(self): + from .actor import LastfmFrontend return [LastfmFrontend] diff --git a/mopidy/frontends/mpd/__init__.py b/mopidy/frontends/mpd/__init__.py index 8d9d13e0..10334bcf 100644 --- a/mopidy/frontends/mpd/__init__.py +++ b/mopidy/frontends/mpd/__init__.py @@ -51,10 +51,6 @@ near future: """ -# TODO Move import into method when FRONTENDS setting is removed -from .actor import MpdFrontend - - class Extension(ext.Extension): name = 'Mopidy-MPD' @@ -70,4 +66,5 @@ class Extension(ext.Extension): pass def get_frontend_classes(self): + from .actor import MpdFrontend return [MpdFrontend] diff --git a/mopidy/frontends/mpris/__init__.py b/mopidy/frontends/mpris/__init__.py index b21dafff..268a9bc2 100644 --- a/mopidy/frontends/mpris/__init__.py +++ b/mopidy/frontends/mpris/__init__.py @@ -58,10 +58,6 @@ Now you can control Mopidy through the player object. Examples: """ -# TODO Move import into method when FRONTENDS setting is removed -from .actor import MprisFrontend - - class Extension(ext.Extension): name = 'Mopidy-MPRIS' @@ -80,4 +76,5 @@ class Extension(ext.Extension): raise ExtensionError('Library dbus not found', e) def get_frontend_classes(self): + from .actor import MprisFrontend return [MprisFrontend] diff --git a/setup.py b/setup.py index cff6ce23..8d3d6d5a 100644 --- a/setup.py +++ b/setup.py @@ -45,12 +45,12 @@ setup( 'mopidy-scan = mopidy.scanner:main', ], b'mopidy.extension': [ - 'http = mopidy.frontends.http:Extension', - 'lastfm = mopidy.frontends.lastfm:Extension', + 'http = mopidy.frontends.http:Extension [http]', + 'lastfm = mopidy.frontends.lastfm:Extension [lastfm]', 'local = mopidy.backends.local:Extension', 'mpd = mopidy.frontends.mpd:Extension', 'mpris = mopidy.frontends.mpris:Extension', - 'spotify = mopidy.backends.spotify:Extension', + 'spotify = mopidy.backends.spotify:Extension [spotify]', 'stream = mopidy.backends.stream:Extension', ], }, diff --git a/tests/backends/local/events_test.py b/tests/backends/local/events_test.py index 79d2780b..b35fad1a 100644 --- a/tests/backends/local/events_test.py +++ b/tests/backends/local/events_test.py @@ -1,12 +1,12 @@ from mopidy import settings -from mopidy.backends.local import LocalBackend +from mopidy.backends.local import actor from tests import unittest, path_to_data_dir from tests.backends.base import events class LocalBackendEventsTest(events.BackendEventsTest, unittest.TestCase): - backend_class = LocalBackend + backend_class = actor.LocalBackend def setUp(self): settings.LOCAL_TAG_CACHE_FILE = path_to_data_dir('empty_tag_cache') diff --git a/tests/backends/local/library_test.py b/tests/backends/local/library_test.py index 7324d85f..7bf8d565 100644 --- a/tests/backends/local/library_test.py +++ b/tests/backends/local/library_test.py @@ -1,7 +1,7 @@ from __future__ import unicode_literals from mopidy import settings -from mopidy.backends.local import LocalBackend +from mopidy.backends.local import actor from tests import unittest, path_to_data_dir from tests.backends.base.library import LibraryControllerTest @@ -9,7 +9,7 @@ from tests.backends.base.library import LibraryControllerTest class LocalLibraryControllerTest(LibraryControllerTest, unittest.TestCase): - backend_class = LocalBackend + backend_class = actor.LocalBackend def setUp(self): settings.LOCAL_TAG_CACHE_FILE = path_to_data_dir('library_tag_cache') diff --git a/tests/backends/local/playback_test.py b/tests/backends/local/playback_test.py index 9a306ee0..834ce8e0 100644 --- a/tests/backends/local/playback_test.py +++ b/tests/backends/local/playback_test.py @@ -1,7 +1,7 @@ from __future__ import unicode_literals from mopidy import settings -from mopidy.backends.local import LocalBackend +from mopidy.backends.local import actor from mopidy.core import PlaybackState from mopidy.models import Track from mopidy.utils.path import path_to_uri @@ -12,7 +12,7 @@ from tests.backends.local import generate_song class LocalPlaybackControllerTest(PlaybackControllerTest, unittest.TestCase): - backend_class = LocalBackend + backend_class = actor.LocalBackend tracks = [ Track(uri=generate_song(i), length=4464) for i in range(1, 4)] diff --git a/tests/backends/local/playlists_test.py b/tests/backends/local/playlists_test.py index 70ed27d6..f3794cee 100644 --- a/tests/backends/local/playlists_test.py +++ b/tests/backends/local/playlists_test.py @@ -3,7 +3,7 @@ from __future__ import unicode_literals import os from mopidy import settings -from mopidy.backends.local import LocalBackend +from mopidy.backends.local import actor from mopidy.models import Track from mopidy.utils.path import path_to_uri @@ -16,7 +16,7 @@ from tests.backends.local import generate_song class LocalPlaylistsControllerTest( PlaylistsControllerTest, unittest.TestCase): - backend_class = LocalBackend + backend_class = actor.LocalBackend def setUp(self): settings.LOCAL_TAG_CACHE_FILE = path_to_data_dir('empty_tag_cache') diff --git a/tests/backends/local/tracklist_test.py b/tests/backends/local/tracklist_test.py index 735043d6..ec09ac83 100644 --- a/tests/backends/local/tracklist_test.py +++ b/tests/backends/local/tracklist_test.py @@ -1,7 +1,7 @@ from __future__ import unicode_literals from mopidy import settings -from mopidy.backends.local import LocalBackend +from mopidy.backends.local import actor from mopidy.models import Track from tests import unittest, path_to_data_dir @@ -10,7 +10,7 @@ from tests.backends.local import generate_song class LocalTracklistControllerTest(TracklistControllerTest, unittest.TestCase): - backend_class = LocalBackend + backend_class = actor.LocalBackend tracks = [ Track(uri=generate_song(i), length=4464) for i in range(1, 4)] diff --git a/tests/frontends/http/events_test.py b/tests/frontends/http/events_test.py index 5c064e93..77438fd4 100644 --- a/tests/frontends/http/events_test.py +++ b/tests/frontends/http/events_test.py @@ -12,7 +12,7 @@ import mock from mopidy.exceptions import OptionalDependencyError try: - from mopidy.frontends.http import HttpFrontend + from mopidy.frontends.http import actor except OptionalDependencyError: pass @@ -24,7 +24,7 @@ from tests import unittest @mock.patch('cherrypy.engine.publish') class HttpEventsTest(unittest.TestCase): def setUp(self): - self.http = HttpFrontend(core=mock.Mock()) + self.http = actor.HttpFrontend(core=mock.Mock()) def test_track_playback_paused_is_broadcasted(self, publish): publish.reset_mock() diff --git a/tests/frontends/mpris/events_test.py b/tests/frontends/mpris/events_test.py index f1add1b3..78e40071 100644 --- a/tests/frontends/mpris/events_test.py +++ b/tests/frontends/mpris/events_test.py @@ -8,7 +8,7 @@ from mopidy.exceptions import OptionalDependencyError from mopidy.models import Playlist, TlTrack try: - from mopidy.frontends.mpris import MprisFrontend, objects + from mopidy.frontends.mpris import actor, objects except OptionalDependencyError: pass @@ -19,7 +19,7 @@ from tests import unittest class BackendEventsTest(unittest.TestCase): def setUp(self): # As a plain class, not an actor: - self.mpris_frontend = MprisFrontend(core=None) + self.mpris_frontend = actor.MprisFrontend(core=None) self.mpris_object = mock.Mock(spec=objects.MprisObject) self.mpris_frontend.mpris_object = self.mpris_object