Merge pull request #377 from jodal/feature/load-extensions
Load backends and frontends from extensions
This commit is contained in:
commit
605f78065a
@ -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__':
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -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]
|
||||
|
||||
6
setup.py
6
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',
|
||||
],
|
||||
},
|
||||
|
||||
@ -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')
|
||||
|
||||
@ -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')
|
||||
|
||||
@ -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)]
|
||||
|
||||
|
||||
@ -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')
|
||||
|
||||
@ -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)]
|
||||
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user