From f89f4151b0f7a037786bdb5d63092dc74dbffac3 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Mon, 1 Apr 2013 11:02:02 +0200 Subject: [PATCH 01/36] ext: Add Extension base class --- mopidy/ext.py | 27 +++++++++++++++++++++++++++ tests/ext_test.py | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 mopidy/ext.py create mode 100644 tests/ext_test.py diff --git a/mopidy/ext.py b/mopidy/ext.py new file mode 100644 index 00000000..dc756a3a --- /dev/null +++ b/mopidy/ext.py @@ -0,0 +1,27 @@ +from __future__ import unicode_literals + + +class Extension(object): + + name = None + version = None + + def get_default_config(self): + raise NotImplementedError( + 'Add at least a config section with "enabled = true"') + + def validate_config(self, config): + raise NotImplementedError( + 'You must explicitly pass config validation if not needed') + + def validate_environment(self): + pass + + def get_frontend_class(self): + pass + + def get_backend_class(self): + pass + + def register_gstreamer_elements(self): + pass diff --git a/tests/ext_test.py b/tests/ext_test.py new file mode 100644 index 00000000..98849e21 --- /dev/null +++ b/tests/ext_test.py @@ -0,0 +1,34 @@ +from __future__ import unicode_literals + +from mopidy.ext import Extension + +from tests import unittest + + +class ExtensionTest(unittest.TestCase): + def setUp(self): + self.ext = Extension() + + def test_name_is_none(self): + self.assertIsNone(self.ext.name) + + def test_version_is_none(self): + self.assertIsNone(self.ext.version) + + def test_get_default_config_raises_not_implemented(self): + self.assertRaises(NotImplementedError, self.ext.get_default_config) + + def test_validate_config_raises_not_implemented(self): + self.assertRaises(NotImplementedError, self.ext.validate_config, None) + + def test_validate_environment_does_nothing_by_default(self): + self.assertIsNone(self.ext.validate_environment()) + + def test_get_frontend_class_returns_none_by_default(self): + self.assertIsNone(self.ext.get_frontend_class()) + + def test_get_backend_class_returns_none_by_default(self): + self.assertIsNone(self.ext.get_backend_class()) + + def test_register_gstreamer_elements_does_nothing_by_default(self): + self.assertIsNone(self.ext.register_gstreamer_elements()) From 059147723bb5aa15615be076928d38d0d75a502e Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Mon, 1 Apr 2013 11:08:05 +0200 Subject: [PATCH 02/36] tests: Test existing exception classes --- tests/exceptions_test.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 tests/exceptions_test.py diff --git a/tests/exceptions_test.py b/tests/exceptions_test.py new file mode 100644 index 00000000..5148ebaf --- /dev/null +++ b/tests/exceptions_test.py @@ -0,0 +1,21 @@ +from __future__ import unicode_literals + +from mopidy import exceptions + +from tests import unittest + + +class ExceptionsTest(unittest.TestCase): + def test_exception_can_include_message_string(self): + exc = exceptions.MopidyException('foo') + + self.assertEqual(exc.message, 'foo') + self.assertEqual(str(exc), 'foo') + + def test_settings_error_is_a_mopidy_exception(self): + self.assert_(issubclass( + exceptions.SettingsError, exceptions.MopidyException)) + + def test_optional_dependency_error_is_a_mopidy_exception(self): + self.assert_(issubclass( + exceptions.OptionalDependencyError, exceptions.MopidyException)) From 0ec989d2cb9bb18c0529f1f7a6767f815488ee17 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Mon, 1 Apr 2013 11:09:16 +0200 Subject: [PATCH 03/36] ext: Add ExtensionError exception class --- mopidy/exceptions.py | 4 ++++ tests/exceptions_test.py | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/mopidy/exceptions.py b/mopidy/exceptions.py index b8d183fb..00c19e9e 100644 --- a/mopidy/exceptions.py +++ b/mopidy/exceptions.py @@ -22,3 +22,7 @@ class SettingsError(MopidyException): class OptionalDependencyError(MopidyException): pass + + +class ExtensionError(MopidyException): + pass diff --git a/tests/exceptions_test.py b/tests/exceptions_test.py index 5148ebaf..2bc838d7 100644 --- a/tests/exceptions_test.py +++ b/tests/exceptions_test.py @@ -19,3 +19,7 @@ class ExceptionsTest(unittest.TestCase): def test_optional_dependency_error_is_a_mopidy_exception(self): self.assert_(issubclass( exceptions.OptionalDependencyError, exceptions.MopidyException)) + + def test_extension_error_is_a_mopidy_exception(self): + self.assert_(issubclass( + exceptions.ExtensionError, exceptions.MopidyException)) From 64b0cc7b98963cbcdd5d704ad075e50547d2715a Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Mon, 1 Apr 2013 11:30:07 +0200 Subject: [PATCH 04/36] ext: Support multiple frontends/backends in an extension --- docs/extensiondev.rst | 8 ++++---- mopidy/ext.py | 8 ++++---- tests/ext_test.py | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/extensiondev.rst b/docs/extensiondev.rst index b1928798..f2c54847 100644 --- a/docs/extensiondev.rst +++ b/docs/extensiondev.rst @@ -265,13 +265,13 @@ meaningful defaults blank, like ``username`` and ``password``. # You will typically only implement one of the next three methods # in a single extension. - def get_frontend_class(self): + def get_frontend_classes(self): from .frontend import SoundspotFrontend - return SoundspotFrontend + return [SoundspotFrontend] - def get_backend_class(self): + def get_backend_classes(self): from .backend import SoundspotBackend - return SoundspotBackend + return [SoundspotBackend] def register_gstreamer_elements(self): from .mixer import SoundspotMixer diff --git a/mopidy/ext.py b/mopidy/ext.py index dc756a3a..6cc35139 100644 --- a/mopidy/ext.py +++ b/mopidy/ext.py @@ -17,11 +17,11 @@ class Extension(object): def validate_environment(self): pass - def get_frontend_class(self): - pass + def get_frontend_classes(self): + return [] - def get_backend_class(self): - pass + def get_backend_classes(self): + return [] def register_gstreamer_elements(self): pass diff --git a/tests/ext_test.py b/tests/ext_test.py index 98849e21..ac238ca5 100644 --- a/tests/ext_test.py +++ b/tests/ext_test.py @@ -24,11 +24,11 @@ class ExtensionTest(unittest.TestCase): def test_validate_environment_does_nothing_by_default(self): self.assertIsNone(self.ext.validate_environment()) - def test_get_frontend_class_returns_none_by_default(self): - self.assertIsNone(self.ext.get_frontend_class()) + def test_get_frontend_classes_returns_an_empty_list(self): + self.assertListEqual(self.ext.get_frontend_classes(), []) - def test_get_backend_class_returns_none_by_default(self): - self.assertIsNone(self.ext.get_backend_class()) + def test_get_backend_classes_returns_an_empty_list(self): + self.assertListEqual(self.ext.get_backend_classes(), []) def test_register_gstreamer_elements_does_nothing_by_default(self): self.assertIsNone(self.ext.register_gstreamer_elements()) From c905134bf4d0bb399cad24352daa39cb5d8827f6 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Mon, 1 Apr 2013 12:49:57 +0200 Subject: [PATCH 05/36] Fix error in MANIFEST.in --- MANIFEST.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MANIFEST.in b/MANIFEST.in index 6a64cb9a..f98952ca 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -7,7 +7,7 @@ include mopidy/backends/spotify/spotify_appkey.key include pylintrc recursive-include docs * prune docs/_build -recursive-include mopidy/frontends/http/data/ +recursive-include mopidy/frontends/http/data * recursive-include requirements * recursive-include tests *.py recursive-include tests/data * From 1dab53d5c885c54062febcf3c73327d7ba2376d2 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Mon, 1 Apr 2013 12:50:14 +0200 Subject: [PATCH 06/36] Ignore *.egg-info files generated by setuptools --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 9229541f..6ef1ff32 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +*.egg-info *.pyc *.swp .coverage From a8b81eadd04a9fe4ca4ffa00db916770e018bdc7 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Mon, 1 Apr 2013 12:52:03 +0200 Subject: [PATCH 07/36] Switch from distutils to setuptools --- docs/changes.rst | 9 ++++ requirements/core.txt | 3 ++ setup.py | 112 +++++++++++------------------------------- 3 files changed, 41 insertions(+), 83 deletions(-) diff --git a/docs/changes.rst b/docs/changes.rst index ee1ea5d7..3ad5b2e1 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -4,6 +4,15 @@ Changes This change log is used to track all major changes to Mopidy. +v0.14.0 (UNRELEASED) +==================== + +**Dependencies** + +- setuptools or distribute is now required. We've introduced this dependency to + use setuptools' entry points functionality to find installed Mopidy + extensions. + v0.13.0 (2013-03-31) ==================== diff --git a/requirements/core.txt b/requirements/core.txt index 9ffac2cf..d8e81e61 100644 --- a/requirements/core.txt +++ b/requirements/core.txt @@ -1,2 +1,5 @@ +setuptools +# Available as python-setuptools in Debian/Ubuntu + Pykka >= 1.1 # Available as python-pykka from apt.mopidy.com diff --git a/setup.py b/setup.py index 5840ca53..1c0d10b8 100644 --- a/setup.py +++ b/setup.py @@ -1,101 +1,47 @@ -""" -Most of this file is taken from the Django project, which is BSD licensed. -""" - from __future__ import unicode_literals -from distutils.core import setup -from distutils.command.install_data import install_data -from distutils.command.install import INSTALL_SCHEMES -import os import re -import sys + +from setuptools import setup, find_packages -def get_version(): - init_py = open('mopidy/__init__.py').read() +def get_version(filename): + init_py = open(filename).read() metadata = dict(re.findall("__([a-z]+)__ = '([^']+)'", init_py)) return metadata['version'] -class osx_install_data(install_data): - # On MacOS, the platform-specific lib dir is - # /System/Library/Framework/Python/.../ which is wrong. Python 2.5 supplied - # with MacOS 10.5 has an Apple-specific fix for this in - # distutils.command.install_data#306. It fixes install_lib but not - # install_data, which is why we roll our own install_data class. - - def finalize_options(self): - # By the time finalize_options is called, install.install_lib is set to - # the fixed directory, so we set the installdir to install_lib. The - # install_data class uses ('install_data', 'install_dir') instead. - self.set_undefined_options('install', ('install_lib', 'install_dir')) - install_data.finalize_options(self) - - -if sys.platform == "darwin": - cmdclasses = {'install_data': osx_install_data} -else: - cmdclasses = {'install_data': install_data} - - -def fullsplit(path, result=None): - """ - Split a pathname into components (the opposite of os.path.join) in a - platform-neutral way. - """ - if result is None: - result = [] - head, tail = os.path.split(path) - if head == '': - return [tail] + result - if head == path: - return result - return fullsplit(head, [tail] + result) - - -# Tell distutils to put the data_files in platform-specific installation -# locations. See here for an explanation: -# http://groups.google.com/group/comp.lang.python/browse_thread/ -# thread/35ec7b2fed36eaec/2105ee4d9e8042cb -for scheme in INSTALL_SCHEMES.values(): - scheme['data'] = scheme['purelib'] - - -# Compile the list of packages available, because distutils doesn't have -# an easy way to do this. -packages, data_files = [], [] -root_dir = os.path.dirname(__file__) -if root_dir != b'': - os.chdir(root_dir) -project_dir = b'mopidy' - -for dirpath, dirnames, filenames in os.walk(project_dir): - # Ignore dirnames that start with '.' - for i, dirname in enumerate(dirnames): - if dirname.startswith(b'.'): - del dirnames[i] - if b'__init__.py' in filenames: - packages.append(b'.'.join(fullsplit(dirpath))) - elif filenames: - data_files.append([ - dirpath, [os.path.join(dirpath, f) for f in filenames]]) - - setup( name='Mopidy', - version=get_version(), - author='Stein Magnus Jodal', - author_email='stein.magnus@jodal.no', - packages=packages, - package_data={b'mopidy': ['backends/spotify/spotify_appkey.key']}, - cmdclass=cmdclasses, - data_files=data_files, - scripts=['bin/mopidy', 'bin/mopidy-scan'], + version=get_version('mopidy/__init__.py'), url='http://www.mopidy.com/', license='Apache License, Version 2.0', + author='Stein Magnus Jodal', + author_email='stein.magnus@jodal.no', description='Music server with MPD and Spotify support', long_description=open('README.rst').read(), + packages=find_packages(exclude=['tests', 'tests.*']), + zip_safe=False, + include_package_data=True, + install_requires=[ + 'setuptools', + 'Pykka >= 1.1', + ], + extras_require={ + b'spotify': ['pyspotify >= 1.9, < 1.11'], + b'lastfm': ['pylast >= 0.5.7'], + b'http': ['cherrypy >= 3.2.2', 'ws4py >= 0.2.3'], + b'external_mixers': ['pyserial'], + }, + test_suite='nose.collector', + tests_require=[ + 'nose', + 'mock >= 0.7', + 'unittest2', + ], + entry_points={ + b'mopidy.extension': [], + }, classifiers=[ 'Development Status :: 4 - Beta', 'Environment :: No Input/Output (Daemon)', From 1a9fea08a589b3e659f1579b3b1b4537542f71e6 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Mon, 1 Apr 2013 12:52:24 +0200 Subject: [PATCH 08/36] docs: setuptools always regenerates MANIFEST --- docs/development.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/development.rst b/docs/development.rst index 59d004fa..4374acf2 100644 --- a/docs/development.rst +++ b/docs/development.rst @@ -359,7 +359,6 @@ Creating releases #. Build package and upload to PyPI:: - rm MANIFEST # Will be regenerated by setup.py python setup.py sdist upload #. Update the Debian package. From a4f8af048d0c3e79b400d99997df8795dc5661df Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Mon, 1 Apr 2013 12:52:53 +0200 Subject: [PATCH 09/36] Let setuptools generate executables --- bin/mopidy | 5 ----- bin/mopidy-scan | 5 ----- setup.py | 4 ++++ 3 files changed, 4 insertions(+), 10 deletions(-) delete mode 100755 bin/mopidy delete mode 100755 bin/mopidy-scan diff --git a/bin/mopidy b/bin/mopidy deleted file mode 100755 index 0472518e..00000000 --- a/bin/mopidy +++ /dev/null @@ -1,5 +0,0 @@ -#! /usr/bin/env python - -if __name__ == '__main__': - from mopidy.__main__ import main - main() diff --git a/bin/mopidy-scan b/bin/mopidy-scan deleted file mode 100755 index 00f51809..00000000 --- a/bin/mopidy-scan +++ /dev/null @@ -1,5 +0,0 @@ -#! /usr/bin/env python - -if __name__ == '__main__': - from mopidy.scanner import main - main() diff --git a/setup.py b/setup.py index 1c0d10b8..eeab24bd 100644 --- a/setup.py +++ b/setup.py @@ -40,6 +40,10 @@ setup( 'unittest2', ], entry_points={ + b'console_scripts': [ + 'mopidy = mopidy.__main__:main', + 'mopidy-scan = mopidy.scanner:main', + ], b'mopidy.extension': [], }, classifiers=[ From 0f593b66bd832b2ca3fbdd206a7b8a03f113ccde Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Mon, 1 Apr 2013 12:53:32 +0200 Subject: [PATCH 10/36] docs: Remove 'platforms' from extension's setup.py example --- docs/extensiondev.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/extensiondev.rst b/docs/extensiondev.rst index f2c54847..afebc6a7 100644 --- a/docs/extensiondev.rst +++ b/docs/extensiondev.rst @@ -162,7 +162,6 @@ class that will connect the rest of the dots. #py_modules=['mopidy_soundspot'], zip_safe=False, include_package_data=True, - platforms='any', install_requires=[ 'setuptools', 'Mopidy', From 5fcae977cabaf0fddee2db67ea912fa512fe0083 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Mon, 1 Apr 2013 13:07:01 +0200 Subject: [PATCH 11/36] docs: If using pip Pykka will be installed automatically --- docs/installation/index.rst | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/docs/installation/index.rst b/docs/installation/index.rst index 2b9806fd..45f93510 100644 --- a/docs/installation/index.rst +++ b/docs/installation/index.rst @@ -135,10 +135,10 @@ Pip. sudo easy_install pip -#. Then get, build, and install the latest releast of pyspotify, pylast, pykka, +#. Then get, build, and install the latest releast of pyspotify, pylast, and Mopidy using Pip:: - sudo pip install -U pyspotify pylast pykka mopidy + sudo pip install -U pyspotify pylast mopidy #. Finally, you need to set a couple of :doc:`settings `, and then you're ready to :doc:`run Mopidy `. @@ -171,15 +171,7 @@ can install Mopidy from PyPI using Pip. sudo yum install -y gcc python-devel python-pip -#. Then you'll need to install all of Mopidy's hard dependencies: - - - Pykka >= 1.0:: - - sudo pip install -U pykka - - On Fedora the binary is called ``pip-python``:: - - sudo pip-python install -U pykka +#. Then you'll need to install all of Mopidy's hard non-Python dependencies: - GStreamer 0.10.x, with Python bindings. GStreamer is packaged for most popular Linux distributions. Search for GStreamer in your package manager, @@ -235,7 +227,8 @@ can install Mopidy from PyPI using Pip. sudo pip install -U pyspotify - # Fedora: + On Fedora the binary is called ``pip-python``:: + sudo pip-python install -U pyspotify #. Optional: If you want to scrobble your played tracks to Last.fm, you need @@ -243,7 +236,8 @@ can install Mopidy from PyPI using Pip. sudo pip install -U pylast - # Fedora: + On Fedora the binary is called ``pip-python``:: + sudo pip-python install -U pylast #. Optional: To use MPRIS, e.g. for controlling Mopidy from the Ubuntu Sound @@ -259,7 +253,8 @@ can install Mopidy from PyPI using Pip. sudo pip install -U mopidy - # Fedora: + On Fedora the binary is called ``pip-python``:: + sudo pip-python install -U mopidy To upgrade Mopidy to future releases, just rerun this command. From 77f8e228d8615909fc8b746e18b3010e52eb37c9 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Mon, 1 Apr 2013 13:19:46 +0200 Subject: [PATCH 12/36] docs: Fix typo --- docs/extensiondev.rst | 2 +- docs/installation/index.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/extensiondev.rst b/docs/extensiondev.rst index afebc6a7..04c6aa6b 100644 --- a/docs/extensiondev.rst +++ b/docs/extensiondev.rst @@ -168,7 +168,7 @@ class that will connect the rest of the dots. 'pysoundspot', ], entry_points={ - 'mopidy.extension': [ + b'mopidy.extension': [ 'soundspot = mopidy_soundspot:Extension', ], }, diff --git a/docs/installation/index.rst b/docs/installation/index.rst index 45f93510..ab81b753 100644 --- a/docs/installation/index.rst +++ b/docs/installation/index.rst @@ -135,7 +135,7 @@ Pip. sudo easy_install pip -#. Then get, build, and install the latest releast of pyspotify, pylast, +#. Then get, build, and install the latest release of pyspotify, pylast, and Mopidy using Pip:: sudo pip install -U pyspotify pylast mopidy From a63bb4eaec979a92dbbc9b76844c61c91ef84052 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Mon, 1 Apr 2013 13:24:40 +0200 Subject: [PATCH 13/36] docs: Include https in links where appropriate --- README.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.rst b/README.rst index 8598b153..b572cdab 100644 --- a/README.rst +++ b/README.rst @@ -18,9 +18,9 @@ platforms, including Windows, Mac OS X, Linux, Android and iOS. To get started with Mopidy, check out `the docs `_. - `Documentation `_ -- `Source code `_ -- `Issue tracker `_ -- `CI server `_ +- `Source code `_ +- `Issue tracker `_ +- `CI server `_ - IRC: ``#mopidy`` at `irc.freenode.net `_ - Mailing list: `mopidy@googlegroups.com `_ -- `Download development snapshot `_ +- `Download development snapshot `_ From 7060a75072089e35a908cbbe5224d3dce57e0826 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Mon, 1 Apr 2013 13:38:51 +0200 Subject: [PATCH 14/36] spotify: Define extension --- mopidy/backends/spotify/__init__.py | 76 ++++++++++++++++++++++++++--- setup.py | 4 +- 2 files changed, 71 insertions(+), 9 deletions(-) diff --git a/mopidy/backends/spotify/__init__.py b/mopidy/backends/spotify/__init__.py index 507511f4..8c65817a 100644 --- a/mopidy/backends/spotify/__init__.py +++ b/mopidy/backends/spotify/__init__.py @@ -1,4 +1,39 @@ -"""A backend for playing music from Spotify +from __future__ import unicode_literals + +import mopidy +from mopidy import ext +from mopidy.exceptions import ExtensionError +from mopidy.utils.formatting import indent + + +config = """ +[spotify] + +# If the Spotify extension should be enabled or not +enabled = true + +# Your Spotify Premium username +username = + +# Your Spotify Premium password +password = + +# The preferred audio bitrate. Valid values are 96, 160, 320 +bitrate = 160 + +# Max number of seconds to wait for Spotify operations to complete +timeout = 10 + +# Path to the Spotify data cache. Cannot be shared with other Spotify apps +cache_path = $XDG_CACHE_DIR/mopidy/spotify + +# Connect to Spotify through a proxy +proxy_host = +proxy_username = +proxy_password = +""" + +__doc__ = """A backend for playing music from Spotify `Spotify `_ is a music streaming service. The backend uses the official `libspotify @@ -22,14 +57,39 @@ https://github.com/mopidy/mopidy/issues?labels=Spotify+backend .. literalinclude:: ../../../requirements/spotify.txt -**Settings:** +**Default config:** -- :attr:`mopidy.settings.SPOTIFY_CACHE_PATH` -- :attr:`mopidy.settings.SPOTIFY_USERNAME` -- :attr:`mopidy.settings.SPOTIFY_PASSWORD` -""" +.. code-block:: ini -from __future__ import unicode_literals +%(config)s +""" % {'config': indent(config)} -# flake8: noqa + +# TODO Move import into method when BACKENDS setting is removed from .actor import SpotifyBackend + + +class Extension(ext.Extension): + + name = 'Mopidy-Spotify' + version = mopidy.__version__ + + def get_default_config(self): + return config + + def validate_config(self, config): + if not config.getboolean('spotify', 'enabled'): + return + if not config.get('spotify', 'username'): + raise ExtensionError('Config spotify.username not set') + if not config.get('spotify', 'password'): + raise ExtensionError('Config spotify.password not set') + + def validate_environment(self): + try: + import spotify # noqa + except ImportError as e: + raise ExtensionError('pyspotify library not found', e) + + def get_backend_classes(self): + return [SpotifyBackend] diff --git a/setup.py b/setup.py index eeab24bd..ec84abd9 100644 --- a/setup.py +++ b/setup.py @@ -44,7 +44,9 @@ setup( 'mopidy = mopidy.__main__:main', 'mopidy-scan = mopidy.scanner:main', ], - b'mopidy.extension': [], + b'mopidy.extension': [ + 'spotify = mopidy.backends.spotify:Extension', + ], }, classifiers=[ 'Development Status :: 4 - Beta', From d8c7b876bed988d706daf33ffb6b3c0da5c28750 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Mon, 1 Apr 2013 14:50:22 +0200 Subject: [PATCH 15/36] local: Define extension --- mopidy/backends/local/__init__.py | 29 ++++++++++++++++++++++++++--- setup.py | 1 + 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/mopidy/backends/local/__init__.py b/mopidy/backends/local/__init__.py index 8ee58d3b..8f7f7be0 100644 --- a/mopidy/backends/local/__init__.py +++ b/mopidy/backends/local/__init__.py @@ -1,4 +1,10 @@ -"""A backend for playing music from a local music archive. +from __future__ import unicode_literals + +import mopidy +from mopidy import ext + + +__doc__ = """A backend for playing music from a local music archive. This backend handles URIs starting with ``file:``. @@ -20,7 +26,24 @@ https://github.com/mopidy/mopidy/issues?labels=Local+backend - :attr:`mopidy.settings.LOCAL_TAG_CACHE_FILE` """ -from __future__ import unicode_literals -# flake8: noqa +# TODO Move import into method when BACKENDS setting is removed from .actor import LocalBackend + + +class Extension(ext.Extension): + + name = 'Mopidy-Local' + version = mopidy.__version__ + + def get_default_config(self): + return '[local]' + + def validate_config(self, config): + pass + + def validate_environment(self): + pass + + def get_backend_classes(self): + return [LocalBackend] diff --git a/setup.py b/setup.py index ec84abd9..132e00a9 100644 --- a/setup.py +++ b/setup.py @@ -45,6 +45,7 @@ setup( 'mopidy-scan = mopidy.scanner:main', ], b'mopidy.extension': [ + 'local = mopidy.backends.local:Extension', 'spotify = mopidy.backends.spotify:Extension', ], }, From d65a986a56c4b72ab0aa373ba51f4fa57203d643 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Mon, 1 Apr 2013 14:56:20 +0200 Subject: [PATCH 16/36] stream: Define extension --- mopidy/backends/stream/__init__.py | 29 ++++++++++++++++++++++++++--- setup.py | 1 + 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/mopidy/backends/stream/__init__.py b/mopidy/backends/stream/__init__.py index 82755540..8c856c10 100644 --- a/mopidy/backends/stream/__init__.py +++ b/mopidy/backends/stream/__init__.py @@ -1,4 +1,10 @@ -"""A backend for playing music for streaming music. +from __future__ import unicode_literals + +import mopidy +from mopidy import ext + + +__doc__ = """A backend for playing music for streaming music. This backend will handle streaming of URIs in :attr:`mopidy.settings.STREAM_PROTOCOLS` assuming the right plugins are @@ -17,7 +23,24 @@ https://github.com/mopidy/mopidy/issues?labels=Stream+backend - :attr:`mopidy.settings.STREAM_PROTOCOLS` """ -from __future__ import unicode_literals -# flake8: noqa +# TODO Move import into method when BACKENDS setting is removed from .actor import StreamBackend + + +class Extension(ext.Extension): + + name = 'Mopidy-Stream' + version = mopidy.__version__ + + def get_default_config(self): + return '[stream]' + + def validate_config(self, config): + pass + + def validate_environment(self): + pass + + def get_backend_classes(self): + return [StreamBackend] diff --git a/setup.py b/setup.py index 132e00a9..340d5058 100644 --- a/setup.py +++ b/setup.py @@ -47,6 +47,7 @@ setup( b'mopidy.extension': [ 'local = mopidy.backends.local:Extension', 'spotify = mopidy.backends.spotify:Extension', + 'stream = mopidy.backends.stream:Extension', ], }, classifiers=[ From a5f3bfc9c43481850da76275300bb83b510d3ea5 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Mon, 1 Apr 2013 15:01:28 +0200 Subject: [PATCH 17/36] mpd: Define extension --- mopidy/frontends/mpd/__init__.py | 29 ++++++++++++++++++++++++++--- setup.py | 1 + 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/mopidy/frontends/mpd/__init__.py b/mopidy/frontends/mpd/__init__.py index 6b4eacc8..e095da14 100644 --- a/mopidy/frontends/mpd/__init__.py +++ b/mopidy/frontends/mpd/__init__.py @@ -1,4 +1,10 @@ -"""The MPD server frontend. +from __future__ import unicode_literals + +import mopidy +from mopidy import ext + + +__doc__ = """The MPD server frontend. MPD stands for Music Player Daemon. MPD is an independent project and server. Mopidy implements the MPD protocol, and is thus compatible with clients for the @@ -44,7 +50,24 @@ near future: - Live update of the music database is not supported """ -from __future__ import unicode_literals -# flake8: noqa +# TODO Move import into method when FRONTENDS setting is removed from .actor import MpdFrontend + + +class Extension(ext.Extension): + + name = 'Mopidy-MPD' + version = mopidy.__version__ + + def get_default_config(self): + return '[mpd]' + + def validate_config(self, config): + pass + + def validate_environment(self): + pass + + def get_frontend_classes(self): + return [MpdFrontend] diff --git a/setup.py b/setup.py index 340d5058..eb46fe87 100644 --- a/setup.py +++ b/setup.py @@ -46,6 +46,7 @@ setup( ], b'mopidy.extension': [ 'local = mopidy.backends.local:Extension', + 'mpd = mopidy.frontends.mpd:Extension', 'spotify = mopidy.backends.spotify:Extension', 'stream = mopidy.backends.stream:Extension', ], From 5b6fc25cf843f29f99921818932a9fb380c9ea7b Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Mon, 1 Apr 2013 15:04:25 +0200 Subject: [PATCH 18/36] mpris: Define extension --- mopidy/frontends/mpris/__init__.py | 33 +++++++++++++++++++++++++++--- setup.py | 1 + 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/mopidy/frontends/mpris/__init__.py b/mopidy/frontends/mpris/__init__.py index 2be6efea..acaff7ff 100644 --- a/mopidy/frontends/mpris/__init__.py +++ b/mopidy/frontends/mpris/__init__.py @@ -1,4 +1,11 @@ -""" +from __future__ import unicode_literals + +import mopidy +from mopidy import ext +from mopidy.exceptions import ExtensionError + + +__doc__ = """ Frontend which lets you control Mopidy through the Media Player Remote Interfacing Specification (`MPRIS `_) D-Bus interface. @@ -50,7 +57,27 @@ Now you can control Mopidy through the player object. Examples: player.Quit(dbus_interface='org.mpris.MediaPlayer2') """ -from __future__ import unicode_literals -# flake8: noqa +# TODO Move import into method when FRONTENDS setting is removed from .actor import MprisFrontend + + +class Extension(ext.Extension): + + name = 'Mopidy-MPRIS' + version = mopidy.__version__ + + def get_default_config(self): + return '[mpris]' + + def validate_config(self, config): + pass + + def validate_environment(self): + try: + import dbus # noqa + except ImportError as e: + raise ExtensionError('Library dbus not found', e) + + def get_frontend_classes(self): + return [MprisFrontend] diff --git a/setup.py b/setup.py index eb46fe87..3672e2f2 100644 --- a/setup.py +++ b/setup.py @@ -47,6 +47,7 @@ setup( b'mopidy.extension': [ 'local = mopidy.backends.local:Extension', 'mpd = mopidy.frontends.mpd:Extension', + 'mpris = mopidy.frontends.mpris:Extension', 'spotify = mopidy.backends.spotify:Extension', 'stream = mopidy.backends.stream:Extension', ], From 0f1c4c1dff1cd364285c6fcff6fec478e308418a Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Mon, 1 Apr 2013 15:06:14 +0200 Subject: [PATCH 19/36] http: Define extension --- mopidy/frontends/http/__init__.py | 38 +++++++++++++++++++++++++++++-- setup.py | 1 + 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/mopidy/frontends/http/__init__.py b/mopidy/frontends/http/__init__.py index e81ddf3f..fb354c09 100644 --- a/mopidy/frontends/http/__init__.py +++ b/mopidy/frontends/http/__init__.py @@ -1,4 +1,11 @@ -""" +from __future__ import unicode_literals + +import mopidy +from mopidy import ext +from mopidy.exceptions import ExtensionError + + +__doc__ = """ The HTTP frontends lets you control Mopidy through HTTP and WebSockets, e.g. from a web based client. @@ -477,5 +484,32 @@ Example to get started with and all events that are emitted. """ -# flake8: noqa + +# TODO Move import into method when FRONTENDS setting is removed from .actor import HttpFrontend + + +class Extension(ext.Extension): + + name = 'Mopidy-HTTP' + version = mopidy.__version__ + + def get_default_config(self): + return '[http]' + + def validate_config(self, config): + pass + + def validate_environment(self): + try: + import cherrypy # noqa + except ImportError as e: + raise ExtensionError('Library cherrypy not found', e) + + try: + import ws4py # noqa + except ImportError as e: + raise ExtensionError('Library ws4py not found', e) + + def get_frontend_classes(self): + return [HttpFrontend] diff --git a/setup.py b/setup.py index 3672e2f2..db2b1932 100644 --- a/setup.py +++ b/setup.py @@ -45,6 +45,7 @@ setup( 'mopidy-scan = mopidy.scanner:main', ], b'mopidy.extension': [ + 'http = mopidy.frontends.http:Extension', 'local = mopidy.backends.local:Extension', 'mpd = mopidy.frontends.mpd:Extension', 'mpris = mopidy.frontends.mpris:Extension', From 2176ab3d98b2f004158df07649be15cbd0f83bb7 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Mon, 1 Apr 2013 15:11:35 +0200 Subject: [PATCH 20/36] lastfm: Define extension --- mopidy/frontends/lastfm/__init__.py | 55 +++++++++++++++++++ .../frontends/{lastfm.py => lastfm/actor.py} | 24 -------- setup.py | 1 + 3 files changed, 56 insertions(+), 24 deletions(-) create mode 100644 mopidy/frontends/lastfm/__init__.py rename mopidy/frontends/{lastfm.py => lastfm/actor.py} (87%) diff --git a/mopidy/frontends/lastfm/__init__.py b/mopidy/frontends/lastfm/__init__.py new file mode 100644 index 00000000..aac27848 --- /dev/null +++ b/mopidy/frontends/lastfm/__init__.py @@ -0,0 +1,55 @@ +from __future__ import unicode_literals + +import mopidy +from mopidy import ext +from mopidy.exceptions import ExtensionError + + +__doc__ = """ +Frontend which scrobbles the music you play to your `Last.fm +`_ profile. + +.. note:: + + This frontend requires a free user account at Last.fm. + +**Dependencies:** + +.. literalinclude:: ../../../requirements/lastfm.txt + +**Settings:** + +- :attr:`mopidy.settings.LASTFM_USERNAME` +- :attr:`mopidy.settings.LASTFM_PASSWORD` + +**Usage:** + +Make sure :attr:`mopidy.settings.FRONTENDS` includes +``mopidy.frontends.lastfm.LastfmFrontend``. By default, the setting includes +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' + version = mopidy.__version__ + + def get_default_config(self): + return '[lastfm]' + + def validate_config(self, config): + pass + + def validate_environment(self): + try: + import pylast # noqa + except ImportError as e: + raise ExtensionError('pylast library not found', e) + + def get_frontend_classes(self): + return [LastfmFrontend] diff --git a/mopidy/frontends/lastfm.py b/mopidy/frontends/lastfm/actor.py similarity index 87% rename from mopidy/frontends/lastfm.py rename to mopidy/frontends/lastfm/actor.py index 61dc306c..60a909e0 100644 --- a/mopidy/frontends/lastfm.py +++ b/mopidy/frontends/lastfm/actor.py @@ -1,27 +1,3 @@ -""" -Frontend which scrobbles the music you play to your `Last.fm -`_ profile. - -.. note:: - - This frontend requires a free user account at Last.fm. - -**Dependencies:** - -.. literalinclude:: ../../../requirements/lastfm.txt - -**Settings:** - -- :attr:`mopidy.settings.LASTFM_USERNAME` -- :attr:`mopidy.settings.LASTFM_PASSWORD` - -**Usage:** - -Make sure :attr:`mopidy.settings.FRONTENDS` includes -``mopidy.frontends.lastfm.LastfmFrontend``. By default, the setting includes -the Last.fm frontend. -""" - from __future__ import unicode_literals import logging diff --git a/setup.py b/setup.py index db2b1932..cff6ce23 100644 --- a/setup.py +++ b/setup.py @@ -46,6 +46,7 @@ setup( ], b'mopidy.extension': [ 'http = mopidy.frontends.http:Extension', + 'lastfm = mopidy.frontends.lastfm:Extension', 'local = mopidy.backends.local:Extension', 'mpd = mopidy.frontends.mpd:Extension', 'mpris = mopidy.frontends.mpris:Extension', From a52ae2091b275e9cd41acbd2e28f8f468be10240 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Mon, 1 Apr 2013 16:06:23 +0200 Subject: [PATCH 21/36] Update Pykka version check --- mopidy/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mopidy/__init__.py b/mopidy/__init__.py index b091ef25..416f4fbf 100644 --- a/mopidy/__init__.py +++ b/mopidy/__init__.py @@ -15,9 +15,9 @@ if not (2, 6) <= sys.version_info < (3,): '.'.join(map(str, sys.version_info[:3]))) if (isinstance(pykka.__version__, basestring) - and not SV('1.0') <= SV(pykka.__version__) < SV('2.0')): + and not SV('1.1') <= SV(pykka.__version__) < SV('2.0')): sys.exit( - 'Mopidy requires Pykka >= 1.0, < 2, but found %s' % pykka.__version__) + 'Mopidy requires Pykka >= 1.1, < 2, but found %s' % pykka.__version__) warnings.filterwarnings('ignore', 'could not open display') From ec7c172c2ec1dee35acad25c6b02692485910f6c Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Mon, 1 Apr 2013 16:14:34 +0200 Subject: [PATCH 22/36] Add ext prefix to config sections --- mopidy/backends/local/__init__.py | 2 +- mopidy/backends/spotify/__init__.py | 2 +- mopidy/backends/stream/__init__.py | 2 +- mopidy/frontends/http/__init__.py | 2 +- mopidy/frontends/lastfm/__init__.py | 2 +- mopidy/frontends/mpd/__init__.py | 2 +- mopidy/frontends/mpris/__init__.py | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/mopidy/backends/local/__init__.py b/mopidy/backends/local/__init__.py index 8f7f7be0..99c50e1f 100644 --- a/mopidy/backends/local/__init__.py +++ b/mopidy/backends/local/__init__.py @@ -37,7 +37,7 @@ class Extension(ext.Extension): version = mopidy.__version__ def get_default_config(self): - return '[local]' + return '[ext.local]' def validate_config(self, config): pass diff --git a/mopidy/backends/spotify/__init__.py b/mopidy/backends/spotify/__init__.py index 8c65817a..efa5338b 100644 --- a/mopidy/backends/spotify/__init__.py +++ b/mopidy/backends/spotify/__init__.py @@ -7,7 +7,7 @@ from mopidy.utils.formatting import indent config = """ -[spotify] +[ext.spotify] # If the Spotify extension should be enabled or not enabled = true diff --git a/mopidy/backends/stream/__init__.py b/mopidy/backends/stream/__init__.py index 8c856c10..dbf3e6d5 100644 --- a/mopidy/backends/stream/__init__.py +++ b/mopidy/backends/stream/__init__.py @@ -34,7 +34,7 @@ class Extension(ext.Extension): version = mopidy.__version__ def get_default_config(self): - return '[stream]' + return '[ext.stream]' def validate_config(self, config): pass diff --git a/mopidy/frontends/http/__init__.py b/mopidy/frontends/http/__init__.py index fb354c09..25fe788f 100644 --- a/mopidy/frontends/http/__init__.py +++ b/mopidy/frontends/http/__init__.py @@ -495,7 +495,7 @@ class Extension(ext.Extension): version = mopidy.__version__ def get_default_config(self): - return '[http]' + return '[ext.http]' def validate_config(self, config): pass diff --git a/mopidy/frontends/lastfm/__init__.py b/mopidy/frontends/lastfm/__init__.py index aac27848..e3f1f5c6 100644 --- a/mopidy/frontends/lastfm/__init__.py +++ b/mopidy/frontends/lastfm/__init__.py @@ -40,7 +40,7 @@ class Extension(ext.Extension): version = mopidy.__version__ def get_default_config(self): - return '[lastfm]' + return '[ext.lastfm]' def validate_config(self, config): pass diff --git a/mopidy/frontends/mpd/__init__.py b/mopidy/frontends/mpd/__init__.py index e095da14..8d9d13e0 100644 --- a/mopidy/frontends/mpd/__init__.py +++ b/mopidy/frontends/mpd/__init__.py @@ -61,7 +61,7 @@ class Extension(ext.Extension): version = mopidy.__version__ def get_default_config(self): - return '[mpd]' + return '[ext.mpd]' def validate_config(self, config): pass diff --git a/mopidy/frontends/mpris/__init__.py b/mopidy/frontends/mpris/__init__.py index acaff7ff..b21dafff 100644 --- a/mopidy/frontends/mpris/__init__.py +++ b/mopidy/frontends/mpris/__init__.py @@ -68,7 +68,7 @@ class Extension(ext.Extension): version = mopidy.__version__ def get_default_config(self): - return '[mpris]' + return '[ext.mpris]' def validate_config(self, config): pass From 211b20c49660d2e04783db346b3ff3a13f40fd56 Mon Sep 17 00:00:00 2001 From: Thomas Refis Date: Mon, 1 Apr 2013 17:56:05 +0200 Subject: [PATCH 23/36] Adds 'Starred' playlist to the playlist catalog Related to issue #326 --- mopidy/backends/spotify/session_manager.py | 1 + mopidy/backends/spotify/translator.py | 4 +--- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/mopidy/backends/spotify/session_manager.py b/mopidy/backends/spotify/session_manager.py index 6f386aae..cedff8d1 100644 --- a/mopidy/backends/spotify/session_manager.py +++ b/mopidy/backends/spotify/session_manager.py @@ -169,6 +169,7 @@ class SpotifySessionManager(process.BaseThread, PyspotifySessionManager): return playlists = map( translator.to_mopidy_playlist, self.session.playlist_container()) + playlists.append(translator.to_mopidy_playlist(self.session.starred())) playlists = filter(None, playlists) self.backend.playlists.playlists = playlists logger.info('Loaded %d Spotify playlist(s)', len(playlists)) diff --git a/mopidy/backends/spotify/translator.py b/mopidy/backends/spotify/translator.py index ba5f85da..cec15200 100644 --- a/mopidy/backends/spotify/translator.py +++ b/mopidy/backends/spotify/translator.py @@ -72,9 +72,7 @@ def to_mopidy_playlist(spotify_playlist): return Playlist(uri=uri, name='[loading...]') name = spotify_playlist.name() if not name: - # Other user's "starred" playlists isn't handled properly by pyspotify - # See https://github.com/mopidy/pyspotify/issues/81 - return + name = "Starred" if spotify_playlist.owner().canonical_name() != settings.SPOTIFY_USERNAME: name += ' by ' + spotify_playlist.owner().canonical_name() return Playlist( From 5982d1059c0a73b26101872cf252c6369ad40ba5 Mon Sep 17 00:00:00 2001 From: Thomas Refis Date: Mon, 1 Apr 2013 18:23:06 +0200 Subject: [PATCH 24/36] Reverse the list of tracks in the Starred playlist. As it is in reverse order from the "official" spotify client. --- mopidy/backends/spotify/translator.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/mopidy/backends/spotify/translator.py b/mopidy/backends/spotify/translator.py index cec15200..cffb4022 100644 --- a/mopidy/backends/spotify/translator.py +++ b/mopidy/backends/spotify/translator.py @@ -71,14 +71,19 @@ def to_mopidy_playlist(spotify_playlist): if not spotify_playlist.is_loaded(): return Playlist(uri=uri, name='[loading...]') name = spotify_playlist.name() + tracks=[ + to_mopidy_track(spotify_track) + for spotify_track in spotify_playlist + if not spotify_track.is_local() + ] if not name: name = "Starred" + # Tracks in the Starred playlist are in reverse order from the official + # client. + tracks.reverse() if spotify_playlist.owner().canonical_name() != settings.SPOTIFY_USERNAME: name += ' by ' + spotify_playlist.owner().canonical_name() return Playlist( uri=uri, name=name, - tracks=[ - to_mopidy_track(spotify_track) - for spotify_track in spotify_playlist - if not spotify_track.is_local()]) + tracks=tracks) From 111d315d1ede7259cb2f2805d932d89b54b09f0d Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Mon, 1 Apr 2013 19:34:39 +0200 Subject: [PATCH 25/36] spotify: Fix style --- mopidy/backends/spotify/translator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mopidy/backends/spotify/translator.py b/mopidy/backends/spotify/translator.py index cffb4022..dfd9d99a 100644 --- a/mopidy/backends/spotify/translator.py +++ b/mopidy/backends/spotify/translator.py @@ -71,13 +71,13 @@ def to_mopidy_playlist(spotify_playlist): if not spotify_playlist.is_loaded(): return Playlist(uri=uri, name='[loading...]') name = spotify_playlist.name() - tracks=[ + tracks = [ to_mopidy_track(spotify_track) for spotify_track in spotify_playlist if not spotify_track.is_local() ] if not name: - name = "Starred" + name = 'Starred' # Tracks in the Starred playlist are in reverse order from the official # client. tracks.reverse() From a7a3803446b4ec6022a3637411eaf35be33db304 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Mon, 1 Apr 2013 19:35:38 +0200 Subject: [PATCH 26/36] docs: Update changelog and authors --- AUTHORS | 1 + docs/changes.rst | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/AUTHORS b/AUTHORS index d6ede848..87925152 100644 --- a/AUTHORS +++ b/AUTHORS @@ -18,3 +18,4 @@ - herrernst - Nick Steel - Zan Dobersek +- Thomas Refis diff --git a/docs/changes.rst b/docs/changes.rst index 3ad5b2e1..0c14db72 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -13,6 +13,11 @@ v0.14.0 (UNRELEASED) use setuptools' entry points functionality to find installed Mopidy extensions. +**Spotify backend** + +- Add support for starred playlists, both your own and those owned by other + users. (Fixes: :issue:`326`) + v0.13.0 (2013-03-31) ==================== From 7d90dd42b54d36bf077121179f44c599ead4f058 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Mon, 1 Apr 2013 18:11:52 +0200 Subject: [PATCH 27/36] main: Load frontends/backends from extensions --- mopidy/__main__.py | 57 +++++++++++++++++++++++++++------------------- 1 file changed, 33 insertions(+), 24 deletions(-) diff --git a/mopidy/__main__.py b/mopidy/__main__.py index a7e914a9..cf67b68f 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,6 +140,17 @@ def setup_settings(interactive): sys.exit(1) +def load_extensions(): + extensions = [] + for entry_point in pkg_resources.iter_entry_points('mopidy.extension'): + extension_class = entry_point.load() + extension = extension_class() + logger.info( + 'Loading extension: %s %s', extension.name, extension.version) + extensions.append(extension) + return extensions + + def setup_audio(): return Audio.start().proxy() @@ -146,18 +159,19 @@ def stop_audio(): process.stop_actors_by_class(Audio) -def setup_backends(audio): +def setup_backends(extensions, audio): 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): + for extension in extensions: + for backend_class in extension.get_backend_classes(): + process.stop_actors_by_class(backend_class) def setup_core(audio, backends): @@ -168,21 +182,16 @@ def stop_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): + 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): + 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__': From 6345022cfcd02772bccd2b52abb2c8a1a0cd62e8 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Mon, 1 Apr 2013 18:21:17 +0200 Subject: [PATCH 28/36] Move frontend/backend import into methods --- mopidy/backends/local/__init__.py | 5 +---- mopidy/backends/spotify/__init__.py | 5 +---- mopidy/backends/stream/__init__.py | 5 +---- mopidy/frontends/http/__init__.py | 5 +---- mopidy/frontends/lastfm/__init__.py | 5 +---- mopidy/frontends/mpd/__init__.py | 5 +---- mopidy/frontends/mpris/__init__.py | 5 +---- tests/backends/local/events_test.py | 4 ++-- tests/backends/local/library_test.py | 4 ++-- tests/backends/local/playback_test.py | 4 ++-- tests/backends/local/playlists_test.py | 4 ++-- tests/backends/local/tracklist_test.py | 4 ++-- tests/frontends/http/events_test.py | 4 ++-- tests/frontends/mpris/events_test.py | 4 ++-- 14 files changed, 21 insertions(+), 42 deletions(-) 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/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 From 8ff7d792d51205192ef2a15a24c4f6bcd4e7422b Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Mon, 1 Apr 2013 18:47:31 +0200 Subject: [PATCH 29/36] main: Validate extension environment --- mopidy/__main__.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/mopidy/__main__.py b/mopidy/__main__.py index cf67b68f..cd3042c7 100644 --- a/mopidy/__main__.py +++ b/mopidy/__main__.py @@ -143,10 +143,22 @@ def setup_settings(interactive): def load_extensions(): extensions = [] for entry_point in pkg_resources.iter_entry_points('mopidy.extension'): + logger.debug('Loading extension %s', entry_point.name) extension_class = entry_point.load() 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( - 'Loading extension: %s %s', extension.name, extension.version) + 'Loaded extension %s: %s %s', + entry_point.name, extension.name, extension.version) extensions.append(extension) return extensions From e1d75eec30a74ed9890965682f64aa94207425d9 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Mon, 1 Apr 2013 18:48:13 +0200 Subject: [PATCH 30/36] main: Validate extension's distribution requirements --- mopidy/__main__.py | 10 +++++++++- setup.py | 6 +++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/mopidy/__main__.py b/mopidy/__main__.py index cd3042c7..faee896c 100644 --- a/mopidy/__main__.py +++ b/mopidy/__main__.py @@ -144,7 +144,15 @@ def load_extensions(): extensions = [] for entry_point in pkg_resources.iter_entry_points('mopidy.extension'): logger.debug('Loading extension %s', entry_point.name) - extension_class = entry_point.load() + + 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 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', ], }, From 396fec5ba87800f4d7a58847f641af7be268d5c7 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Mon, 1 Apr 2013 18:48:37 +0200 Subject: [PATCH 31/36] main: Log a bit more on startup and shutdown --- mopidy/__main__.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/mopidy/__main__.py b/mopidy/__main__.py index faee896c..f0ee6085 100644 --- a/mopidy/__main__.py +++ b/mopidy/__main__.py @@ -172,14 +172,17 @@ def load_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(extensions, audio): + logger.info('Starting Mopidy backends') backends = [] for extension in extensions: for backend_class in extension.get_backend_classes(): @@ -189,26 +192,31 @@ def setup_backends(extensions, audio): 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(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(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) From eef148d3af7e77eb5246ca5a21461aad28437c6b Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Mon, 1 Apr 2013 20:01:36 +0200 Subject: [PATCH 32/36] main: Remove unused import --- mopidy/__main__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mopidy/__main__.py b/mopidy/__main__.py index f0ee6085..f3a95bfb 100644 --- a/mopidy/__main__.py +++ b/mopidy/__main__.py @@ -37,8 +37,7 @@ from mopidy import exceptions, settings from mopidy.audio import Audio from mopidy.core import Core from mopidy.utils import ( - deps, importing, log, path, process, settings as settings_utils, - versioning) + deps, log, path, process, settings as settings_utils, versioning) logger = logging.getLogger('mopidy.main') From f446c323bd605c84faaacd70b4ac778c84f5174b Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Mon, 1 Apr 2013 20:03:26 +0200 Subject: [PATCH 33/36] main: Move TODO --- mopidy/__main__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mopidy/__main__.py b/mopidy/__main__.py index f3a95bfb..3831bd7e 100644 --- a/mopidy/__main__.py +++ b/mopidy/__main__.py @@ -144,6 +144,8 @@ def load_extensions(): for entry_point in pkg_resources.iter_entry_points('mopidy.extension'): logger.debug('Loading extension %s', entry_point.name) + # TODO Filter out disabled extensions + try: extension_class = entry_point.load() except pkg_resources.DistributionNotFound as ex: @@ -154,7 +156,7 @@ def load_extensions(): extension = extension_class() - # TODO Validate configuration, filter out disabled extensions + # TODO Validate configuration try: extension.validate_environment() From 74788a2ccb6bade0d22ad5b235f924a3488f58fa Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Mon, 1 Apr 2013 20:14:20 +0200 Subject: [PATCH 34/36] Remove the BACKENDS setting --- docs/development.rst | 8 +------- mopidy/settings.py | 19 ------------------- mopidy/utils/settings.py | 2 -- tests/backends/local/playback_test.py | 1 - tests/backends/local/tracklist_test.py | 1 - tests/utils/settings_test.py | 7 ------- 6 files changed, 1 insertion(+), 37 deletions(-) diff --git a/docs/development.rst b/docs/development.rst index 4374acf2..776b004d 100644 --- a/docs/development.rst +++ b/docs/development.rst @@ -281,12 +281,6 @@ settings file in the following way:: import os profile = os.environ.get('PROFILE', '').split(',') - if 'spotify' in profile: - BACKENDS = (u'mopidy.backends.spotify.SpotifyBackend',) - elif 'local' in profile: - BACKENDS = (u'mopidy.backends.local.LocalBackend',) - LOCAL_MUSIC_PATH = u'~/music' - if 'shoutcast' in profile: OUTPUT = u'lame ! shout2send mount="/stream"' elif 'silent' in profile: @@ -296,7 +290,7 @@ settings file in the following way:: SPOTIFY_USERNAME = u'xxxxx' SPOTIFY_PASSWORD = u'xxxxx' -Using this setup you can now run Mopidy with ``PROFILE=silent,spotify mopidy`` +Using this setup you can now run Mopidy with ``PROFILE=silent mopidy`` if you for instance want to test Spotify without any actual audio output. diff --git a/mopidy/settings.py b/mopidy/settings.py index d0d279c2..7df43ae5 100644 --- a/mopidy/settings.py +++ b/mopidy/settings.py @@ -9,25 +9,6 @@ All available settings and their default values. from __future__ import unicode_literals -#: List of playback backends to use. See :ref:`backend-implementations` for all -#: available backends. -#: -#: When results from multiple backends are combined, they are combined in the -#: order the backends are listed here. -#: -#: Default:: -#: -#: BACKENDS = ( -#: u'mopidy.backends.local.LocalBackend', -#: u'mopidy.backends.spotify.SpotifyBackend', -#: u'mopidy.backends.stream.StreamBackend', -#: ) -BACKENDS = ( - 'mopidy.backends.local.LocalBackend', - 'mopidy.backends.spotify.SpotifyBackend', - 'mopidy.backends.stream.StreamBackend', -) - #: The log format used for informational logging. #: #: See http://docs.python.org/2/library/logging.html#formatter-objects for diff --git a/mopidy/utils/settings.py b/mopidy/utils/settings.py index 8ae61e5b..011b723a 100644 --- a/mopidy/utils/settings.py +++ b/mopidy/utils/settings.py @@ -143,13 +143,11 @@ def validate_settings(defaults, settings): } must_be_iterable = [ - 'BACKENDS', 'FRONTENDS', 'STREAM_PROTOCOLS', ] must_have_value_set = [ - 'BACKENDS', 'FRONTENDS', ] diff --git a/tests/backends/local/playback_test.py b/tests/backends/local/playback_test.py index 834ce8e0..8d997d2e 100644 --- a/tests/backends/local/playback_test.py +++ b/tests/backends/local/playback_test.py @@ -17,7 +17,6 @@ class LocalPlaybackControllerTest(PlaybackControllerTest, unittest.TestCase): Track(uri=generate_song(i), length=4464) for i in range(1, 4)] def setUp(self): - settings.BACKENDS = ('mopidy.backends.local.LocalBackend',) settings.LOCAL_TAG_CACHE_FILE = path_to_data_dir('empty_tag_cache') super(LocalPlaybackControllerTest, self).setUp() diff --git a/tests/backends/local/tracklist_test.py b/tests/backends/local/tracklist_test.py index ec09ac83..0c47a5db 100644 --- a/tests/backends/local/tracklist_test.py +++ b/tests/backends/local/tracklist_test.py @@ -15,7 +15,6 @@ class LocalTracklistControllerTest(TracklistControllerTest, unittest.TestCase): Track(uri=generate_song(i), length=4464) for i in range(1, 4)] def setUp(self): - settings.BACKENDS = ('mopidy.backends.local.LocalBackend',) settings.LOCAL_TAG_CACHE_FILE = path_to_data_dir('empty_tag_cache') super(LocalTracklistControllerTest, self).setUp() diff --git a/tests/utils/settings_test.py b/tests/utils/settings_test.py index 51f0d89c..4e7d9d77 100644 --- a/tests/utils/settings_test.py +++ b/tests/utils/settings_test.py @@ -11,7 +11,6 @@ from tests import unittest class ValidateSettingsTest(unittest.TestCase): def setUp(self): self.defaults = { - 'BACKENDS': ['a'], 'FRONTENDS': ['a'], 'MPD_SERVER_HOSTNAME': '::', 'MPD_SERVER_PORT': 6600, @@ -81,12 +80,6 @@ class ValidateSettingsTest(unittest.TestCase): self.assertEqual( result['FRONTENDS'], 'Must be set.') - def test_empty_backends_list_returns_error(self): - result = setting_utils.validate_settings( - self.defaults, {'BACKENDS': []}) - self.assertEqual( - result['BACKENDS'], 'Must be set.') - def test_noniterable_multivalue_setting_returns_error(self): result = setting_utils.validate_settings( self.defaults, {'FRONTENDS': ('this is not a tuple')}) From aa1f8199c2adc0cbdc912e70792a01e39f909712 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Mon, 1 Apr 2013 20:22:29 +0200 Subject: [PATCH 35/36] Remove the FRONTENDS setting --- docs/api/frontends.rst | 4 ++-- docs/clients/mpris.rst | 11 +++++------ docs/extensiondev.rst | 2 ++ mopidy/frontends/http/__init__.py | 17 +++++++++-------- mopidy/frontends/lastfm/__init__.py | 4 +--- mopidy/frontends/mpd/__init__.py | 4 +--- mopidy/frontends/mpris/__init__.py | 4 +--- mopidy/settings.py | 16 ---------------- mopidy/utils/settings.py | 9 --------- tests/utils/settings_test.py | 15 --------------- 10 files changed, 21 insertions(+), 65 deletions(-) diff --git a/docs/api/frontends.rst b/docs/api/frontends.rst index 8488b408..a6e6f500 100644 --- a/docs/api/frontends.rst +++ b/docs/api/frontends.rst @@ -20,8 +20,8 @@ The following requirements applies to any frontend implementation: - It MAY use additional actors to implement whatever it does, and using actors in frontend implementations is encouraged. -- The frontend is activated by including its main actor in the - :attr:`mopidy.settings.FRONTENDS` setting. +- The frontend is enabled if the extension it is part of is enabled. See + :ref:`extensiondev` for more information. - The main actor MUST be able to start and stop the frontend when the main actor is started and stopped. diff --git a/docs/clients/mpris.rst b/docs/clients/mpris.rst index c782fa26..28da63ed 100644 --- a/docs/clients/mpris.rst +++ b/docs/clients/mpris.rst @@ -36,12 +36,11 @@ Mopidy executable. If this isn't in place, the sound menu will not detect that Mopidy is running. Next, Mopidy's MPRIS frontend must be running for the sound menu to be able to -control Mopidy. The frontend is activated by default, so unless you've changed -the :attr:`mopidy.settings.FRONTENDS` setting, you should be good to go. Keep -an eye out for warnings or errors from the MPRIS frontend when you start -Mopidy, since it may fail because of missing dependencies or because Mopidy is -started outside of X; the frontend won't work if ``$DISPLAY`` isn't set when -Mopidy is started. +control Mopidy. The frontend is enabled by default, so as long as you have all +its dependencies available, you should be good to go. Keep an eye out for +warnings or errors from the MPRIS frontend when you start Mopidy, since it may +fail because of missing dependencies or because Mopidy is started outside of X; +the frontend won't work if ``$DISPLAY`` isn't set when Mopidy is started. Under normal use, if Mopidy isn't running and you open the menu and click on "Mopidy Music Server", a terminal window will open and automatically start diff --git a/docs/extensiondev.rst b/docs/extensiondev.rst index 04c6aa6b..976f8f84 100644 --- a/docs/extensiondev.rst +++ b/docs/extensiondev.rst @@ -1,3 +1,5 @@ +.. _extensiondev: + ********************* Extension development ********************* diff --git a/mopidy/frontends/http/__init__.py b/mopidy/frontends/http/__init__.py index 0107e357..7b99efd0 100644 --- a/mopidy/frontends/http/__init__.py +++ b/mopidy/frontends/http/__init__.py @@ -25,8 +25,10 @@ from a web based client. Setup ===== -When this frontend is included in :attr:`mopidy.settings.FRONTENDS`, it starts -a web server at the port specified by :attr:`mopidy.settings.HTTP_SERVER_PORT`. +The frontend is enabled by default if all dependencies are available. + +When it is enabled it starts a web server at the port specified by +:attr:`mopidy.settings.HTTP_SERVER_PORT`. .. warning:: Security @@ -364,15 +366,14 @@ event listeners, and delete the object like this: Example to get started with --------------------------- -1. Create an empty directory for your web client. +1. Make sure that you've installed all dependencies required by the HTTP + frontend. -2. Change the setting :attr:`mopidy.settings.HTTP_SERVER_STATIC_DIR` to point +2. Create an empty directory for your web client. + +3. Change the setting :attr:`mopidy.settings.HTTP_SERVER_STATIC_DIR` to point to your new directory. -3. Make sure that you've included - ``mopidy.frontends.http.HttpFrontend`` in - :attr:`mopidy.settings.FRONTENDS`. - 4. Start/restart Mopidy. 5. Create a file in the directory named ``index.html`` containing e.g. "Hello, diff --git a/mopidy/frontends/lastfm/__init__.py b/mopidy/frontends/lastfm/__init__.py index df41d130..439ada50 100644 --- a/mopidy/frontends/lastfm/__init__.py +++ b/mopidy/frontends/lastfm/__init__.py @@ -24,9 +24,7 @@ Frontend which scrobbles the music you play to your `Last.fm **Usage:** -Make sure :attr:`mopidy.settings.FRONTENDS` includes -``mopidy.frontends.lastfm.LastfmFrontend``. By default, the setting includes -the Last.fm frontend. +The frontend is enabled by default if all dependencies are available. """ diff --git a/mopidy/frontends/mpd/__init__.py b/mopidy/frontends/mpd/__init__.py index 10334bcf..5cb8b8c0 100644 --- a/mopidy/frontends/mpd/__init__.py +++ b/mopidy/frontends/mpd/__init__.py @@ -22,9 +22,7 @@ original MPD server. **Usage:** -Make sure :attr:`mopidy.settings.FRONTENDS` includes -``mopidy.frontends.mpd.MpdFrontend``. By default, the setting includes the MPD -frontend. +The frontend is enabled by default. **Limitations:** diff --git a/mopidy/frontends/mpris/__init__.py b/mopidy/frontends/mpris/__init__.py index 268a9bc2..940c4210 100644 --- a/mopidy/frontends/mpris/__init__.py +++ b/mopidy/frontends/mpris/__init__.py @@ -32,9 +32,7 @@ An example of an MPRIS client is the `Ubuntu Sound Menu **Usage:** -Make sure :attr:`mopidy.settings.FRONTENDS` includes -``mopidy.frontends.mpris.MprisFrontend``. By default, the setting includes the -MPRIS frontend. +The frontend is enabled by default if all dependencies are available. **Testing the frontend** diff --git a/mopidy/settings.py b/mopidy/settings.py index 7df43ae5..cde6430a 100644 --- a/mopidy/settings.py +++ b/mopidy/settings.py @@ -39,22 +39,6 @@ DEBUG_LOG_FILENAME = 'mopidy.log' #: DESKTOP_FILE = u'/usr/share/applications/mopidy.desktop' DESKTOP_FILE = '/usr/share/applications/mopidy.desktop' -#: List of server frontends to use. See :ref:`frontend-implementations` for -#: available frontends. -#: -#: Default:: -#: -#: FRONTENDS = ( -#: u'mopidy.frontends.mpd.MpdFrontend', -#: u'mopidy.frontends.lastfm.LastfmFrontend', -#: u'mopidy.frontends.mpris.MprisFrontend', -#: ) -FRONTENDS = ( - 'mopidy.frontends.mpd.MpdFrontend', - 'mopidy.frontends.lastfm.LastfmFrontend', - 'mopidy.frontends.mpris.MprisFrontend', -) - #: Which address Mopidy's HTTP server should bind to. #: #: Used by :mod:`mopidy.frontends.http`. diff --git a/mopidy/utils/settings.py b/mopidy/utils/settings.py index 011b723a..051f0f1c 100644 --- a/mopidy/utils/settings.py +++ b/mopidy/utils/settings.py @@ -123,7 +123,6 @@ def validate_settings(defaults, settings): changed = { 'DUMP_LOG_FILENAME': 'DEBUG_LOG_FILENAME', 'DUMP_LOG_FORMAT': 'DEBUG_LOG_FORMAT', - 'FRONTEND': 'FRONTENDS', 'GSTREAMER_AUDIO_SINK': 'OUTPUT', 'LOCAL_MUSIC_FOLDER': 'LOCAL_MUSIC_PATH', 'LOCAL_OUTPUT_OVERRIDE': 'OUTPUT', @@ -143,14 +142,9 @@ def validate_settings(defaults, settings): } must_be_iterable = [ - 'FRONTENDS', 'STREAM_PROTOCOLS', ] - must_have_value_set = [ - 'FRONTENDS', - ] - for setting, value in settings.iteritems(): if setting in changed: if changed[setting] is None: @@ -180,9 +174,6 @@ def validate_settings(defaults, settings): 'Must be a tuple. ' "Remember the comma after single values: (u'value',)") - elif setting in must_have_value_set and not value: - errors[setting] = 'Must be set.' - elif setting not in defaults and not setting.startswith('CUSTOM_'): errors[setting] = 'Unknown setting.' suggestion = did_you_mean(setting, defaults) diff --git a/tests/utils/settings_test.py b/tests/utils/settings_test.py index 4e7d9d77..2c13066c 100644 --- a/tests/utils/settings_test.py +++ b/tests/utils/settings_test.py @@ -11,7 +11,6 @@ from tests import unittest class ValidateSettingsTest(unittest.TestCase): def setUp(self): self.defaults = { - 'FRONTENDS': ['a'], 'MPD_SERVER_HOSTNAME': '::', 'MPD_SERVER_PORT': 6600, 'SPOTIFY_BITRATE': 160, @@ -74,20 +73,6 @@ class ValidateSettingsTest(unittest.TestCase): 'SPOTIFY_USERNAME', None) self.assertEqual(None, not_secret) - def test_empty_frontends_list_returns_error(self): - result = setting_utils.validate_settings( - self.defaults, {'FRONTENDS': []}) - self.assertEqual( - result['FRONTENDS'], 'Must be set.') - - def test_noniterable_multivalue_setting_returns_error(self): - result = setting_utils.validate_settings( - self.defaults, {'FRONTENDS': ('this is not a tuple')}) - self.assertEqual( - result['FRONTENDS'], - 'Must be a tuple. ' - "Remember the comma after single values: (u'value',)") - class SettingsProxyTest(unittest.TestCase): def setUp(self): From bddb90e49012ec0f839fce45bd7e59003b39ec1f Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Mon, 1 Apr 2013 20:44:23 +0200 Subject: [PATCH 36/36] docs: Add 'ext.' prefix to config sections --- docs/extensiondev.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/extensiondev.rst b/docs/extensiondev.rst index 976f8f84..0505cec8 100644 --- a/docs/extensiondev.rst +++ b/docs/extensiondev.rst @@ -203,7 +203,8 @@ The default configuration for the extension is defined by the ``get_default_config()`` method in the ``Extension`` class which returns a :mod:`ConfigParser` compatible config section. The config section's name should be the same as the extension's short name, as defined in the ``entry_points`` -part of ``setup.py``. All extensions should include an ``enabled`` config which +part of ``setup.py``, but prefixed with ``ext.``, for example +``ext.soundspot``. All extensions should include an ``enabled`` config which should default to ``true``. Provide good defaults for all config values so that as few users as possible will need to change them. The exception is if the config value has security implications; in that case you should default to the @@ -235,7 +236,7 @@ meaningful defaults blank, like ``username`` and ``password``. def get_default_config(self): return """ - [soundspot] + [ext.soundspot] enabled = true username = password =