Merge branch 'develop' into feature/config
This commit is contained in:
commit
50980723f8
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,3 +1,4 @@
|
||||
*.egg-info
|
||||
*.pyc
|
||||
*.swp
|
||||
.coverage
|
||||
|
||||
1
AUTHORS
1
AUTHORS
@ -18,3 +18,4 @@
|
||||
- herrernst <herr.ernst@gmail.com>
|
||||
- Nick Steel <kingosticks@gmail.com>
|
||||
- Zan Dobersek <zandobersek@gmail.com>
|
||||
- Thomas Refis <refis.thomas@gmail.com>
|
||||
|
||||
@ -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 *
|
||||
|
||||
@ -18,9 +18,9 @@ platforms, including Windows, Mac OS X, Linux, Android and iOS.
|
||||
To get started with Mopidy, check out `the docs <http://docs.mopidy.com/>`_.
|
||||
|
||||
- `Documentation <http://docs.mopidy.com/>`_
|
||||
- `Source code <http://github.com/mopidy/mopidy>`_
|
||||
- `Issue tracker <http://github.com/mopidy/mopidy/issues>`_
|
||||
- `CI server <http://travis-ci.org/mopidy/mopidy>`_
|
||||
- `Source code <https://github.com/mopidy/mopidy>`_
|
||||
- `Issue tracker <https://github.com/mopidy/mopidy/issues>`_
|
||||
- `CI server <https://travis-ci.org/mopidy/mopidy>`_
|
||||
- IRC: ``#mopidy`` at `irc.freenode.net <http://freenode.net/>`_
|
||||
- Mailing list: `mopidy@googlegroups.com <https://groups.google.com/forum/?fromgroups=#!forum/mopidy>`_
|
||||
- `Download development snapshot <http://github.com/mopidy/mopidy/tarball/develop#egg=mopidy-dev>`_
|
||||
- `Download development snapshot <https://github.com/mopidy/mopidy/tarball/develop#egg=mopidy-dev>`_
|
||||
|
||||
@ -1,5 +0,0 @@
|
||||
#! /usr/bin/env python
|
||||
|
||||
if __name__ == '__main__':
|
||||
from mopidy.__main__ import main
|
||||
main()
|
||||
@ -1,5 +0,0 @@
|
||||
#! /usr/bin/env python
|
||||
|
||||
if __name__ == '__main__':
|
||||
from mopidy.scanner import main
|
||||
main()
|
||||
@ -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.
|
||||
|
||||
@ -4,6 +4,20 @@ 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.
|
||||
|
||||
**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)
|
||||
====================
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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.
|
||||
|
||||
|
||||
@ -359,7 +353,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.
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
.. _extensiondev:
|
||||
|
||||
*********************
|
||||
Extension development
|
||||
*********************
|
||||
@ -162,14 +164,13 @@ 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',
|
||||
'pysoundspot',
|
||||
],
|
||||
entry_points={
|
||||
'mopidy.extension': [
|
||||
b'mopidy.extension': [
|
||||
'soundspot = mopidy_soundspot:Extension',
|
||||
],
|
||||
},
|
||||
@ -202,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
|
||||
@ -234,7 +236,7 @@ meaningful defaults blank, like ``username`` and ``password``.
|
||||
|
||||
def get_default_config(self):
|
||||
return """
|
||||
[soundspot]
|
||||
[ext.soundspot]
|
||||
enabled = true
|
||||
username =
|
||||
password =
|
||||
@ -265,13 +267,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
|
||||
|
||||
@ -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 release 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 </settings>`, and then
|
||||
you're ready to :doc:`run Mopidy </running>`.
|
||||
@ -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.
|
||||
|
||||
@ -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')
|
||||
|
||||
@ -9,6 +9,7 @@ import sys
|
||||
import gobject
|
||||
gobject.threads_init()
|
||||
|
||||
import pkg_resources
|
||||
import pykka.debug
|
||||
|
||||
|
||||
@ -36,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')
|
||||
@ -54,10 +54,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 +68,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 +139,88 @@ 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)
|
||||
|
||||
# TODO Filter out disabled extensions
|
||||
|
||||
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
|
||||
|
||||
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__':
|
||||
|
||||
@ -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,21 @@ https://github.com/mopidy/mopidy/issues?labels=Local+backend
|
||||
- :attr:`mopidy.settings.LOCAL_TAG_CACHE_FILE`
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
# flake8: noqa
|
||||
from .actor import LocalBackend
|
||||
class Extension(ext.Extension):
|
||||
|
||||
name = 'Mopidy-Local'
|
||||
version = mopidy.__version__
|
||||
|
||||
def get_default_config(self):
|
||||
return '[ext.local]'
|
||||
|
||||
def validate_config(self, config):
|
||||
pass
|
||||
|
||||
def validate_environment(self):
|
||||
pass
|
||||
|
||||
def get_backend_classes(self):
|
||||
from .actor import LocalBackend
|
||||
return [LocalBackend]
|
||||
|
||||
@ -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 = """
|
||||
[ext.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 <http://www.spotify.com/>`_ is a music streaming service. The backend
|
||||
uses the official `libspotify
|
||||
@ -22,14 +57,36 @@ 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
|
||||
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):
|
||||
from .actor import SpotifyBackend
|
||||
return [SpotifyBackend]
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -71,16 +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:
|
||||
# Other user's "starred" playlists isn't handled properly by pyspotify
|
||||
# See https://github.com/mopidy/pyspotify/issues/81
|
||||
return
|
||||
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)
|
||||
|
||||
@ -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,21 @@ https://github.com/mopidy/mopidy/issues?labels=Stream+backend
|
||||
- :attr:`mopidy.settings.STREAM_PROTOCOLS`
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
# flake8: noqa
|
||||
from .actor import StreamBackend
|
||||
class Extension(ext.Extension):
|
||||
|
||||
name = 'Mopidy-Stream'
|
||||
version = mopidy.__version__
|
||||
|
||||
def get_default_config(self):
|
||||
return '[ext.stream]'
|
||||
|
||||
def validate_config(self, config):
|
||||
pass
|
||||
|
||||
def validate_environment(self):
|
||||
pass
|
||||
|
||||
def get_backend_classes(self):
|
||||
from .actor import StreamBackend
|
||||
return [StreamBackend]
|
||||
|
||||
@ -37,3 +37,7 @@ class ConfigError(MopidyException):
|
||||
|
||||
class OptionalDependencyError(MopidyException):
|
||||
pass
|
||||
|
||||
|
||||
class ExtensionError(MopidyException):
|
||||
pass
|
||||
|
||||
27
mopidy/ext.py
Normal file
27
mopidy/ext.py
Normal file
@ -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_classes(self):
|
||||
return []
|
||||
|
||||
def get_backend_classes(self):
|
||||
return []
|
||||
|
||||
def register_gstreamer_elements(self):
|
||||
pass
|
||||
@ -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.
|
||||
|
||||
@ -18,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
|
||||
|
||||
@ -357,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,
|
||||
@ -477,5 +485,29 @@ Example to get started with
|
||||
and all events that are emitted.
|
||||
"""
|
||||
|
||||
# flake8: noqa
|
||||
from .actor import HttpFrontend
|
||||
|
||||
class Extension(ext.Extension):
|
||||
|
||||
name = 'Mopidy-HTTP'
|
||||
version = mopidy.__version__
|
||||
|
||||
def get_default_config(self):
|
||||
return '[ext.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):
|
||||
from .actor import HttpFrontend
|
||||
return [HttpFrontend]
|
||||
|
||||
50
mopidy/frontends/lastfm/__init__.py
Normal file
50
mopidy/frontends/lastfm/__init__.py
Normal file
@ -0,0 +1,50 @@
|
||||
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
|
||||
<http://www.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:**
|
||||
|
||||
The frontend is enabled by default if all dependencies are available.
|
||||
"""
|
||||
|
||||
|
||||
class Extension(ext.Extension):
|
||||
|
||||
name = 'Mopidy-Lastfm'
|
||||
version = mopidy.__version__
|
||||
|
||||
def get_default_config(self):
|
||||
return '[ext.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):
|
||||
from .actor import LastfmFrontend
|
||||
return [LastfmFrontend]
|
||||
@ -1,27 +1,3 @@
|
||||
"""
|
||||
Frontend which scrobbles the music you play to your `Last.fm
|
||||
<http://www.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
|
||||
@ -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
|
||||
@ -16,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:**
|
||||
|
||||
@ -44,7 +48,21 @@ near future:
|
||||
- Live update of the music database is not supported
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
# flake8: noqa
|
||||
from .actor import MpdFrontend
|
||||
class Extension(ext.Extension):
|
||||
|
||||
name = 'Mopidy-MPD'
|
||||
version = mopidy.__version__
|
||||
|
||||
def get_default_config(self):
|
||||
return '[ext.mpd]'
|
||||
|
||||
def validate_config(self, config):
|
||||
pass
|
||||
|
||||
def validate_environment(self):
|
||||
pass
|
||||
|
||||
def get_frontend_classes(self):
|
||||
from .actor import MpdFrontend
|
||||
return [MpdFrontend]
|
||||
|
||||
@ -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 <http://www.mpris.org/>`_) D-Bus
|
||||
interface.
|
||||
@ -25,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**
|
||||
|
||||
@ -50,7 +55,24 @@ Now you can control Mopidy through the player object. Examples:
|
||||
player.Quit(dbus_interface='org.mpris.MediaPlayer2')
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
# flake8: noqa
|
||||
from .actor import MprisFrontend
|
||||
class Extension(ext.Extension):
|
||||
|
||||
name = 'Mopidy-MPRIS'
|
||||
version = mopidy.__version__
|
||||
|
||||
def get_default_config(self):
|
||||
return '[ext.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):
|
||||
from .actor import MprisFrontend
|
||||
return [MprisFrontend]
|
||||
|
||||
@ -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
|
||||
@ -58,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`.
|
||||
|
||||
@ -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,16 +142,9 @@ def validate_settings(defaults, settings):
|
||||
}
|
||||
|
||||
must_be_iterable = [
|
||||
'BACKENDS',
|
||||
'FRONTENDS',
|
||||
'STREAM_PROTOCOLS',
|
||||
]
|
||||
|
||||
must_have_value_set = [
|
||||
'BACKENDS',
|
||||
'FRONTENDS',
|
||||
]
|
||||
|
||||
for setting, value in settings.iteritems():
|
||||
if setting in changed:
|
||||
if changed[setting] is None:
|
||||
@ -182,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)
|
||||
|
||||
@ -1,2 +1,5 @@
|
||||
setuptools
|
||||
# Available as python-setuptools in Debian/Ubuntu
|
||||
|
||||
Pykka >= 1.1
|
||||
# Available as python-pykka from apt.mopidy.com
|
||||
|
||||
124
setup.py
124
setup.py
@ -1,101 +1,59 @@
|
||||
"""
|
||||
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'console_scripts': [
|
||||
'mopidy = mopidy.__main__:main',
|
||||
'mopidy-scan = mopidy.scanner:main',
|
||||
],
|
||||
b'mopidy.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]',
|
||||
'stream = mopidy.backends.stream:Extension',
|
||||
],
|
||||
},
|
||||
classifiers=[
|
||||
'Development Status :: 4 - Beta',
|
||||
'Environment :: No Input/Output (Daemon)',
|
||||
|
||||
@ -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,12 +12,11 @@ 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)]
|
||||
|
||||
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()
|
||||
|
||||
|
||||
@ -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,12 +10,11 @@ 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)]
|
||||
|
||||
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()
|
||||
|
||||
|
||||
25
tests/exceptions_test.py
Normal file
25
tests/exceptions_test.py
Normal file
@ -0,0 +1,25 @@
|
||||
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))
|
||||
|
||||
def test_extension_error_is_a_mopidy_exception(self):
|
||||
self.assert_(issubclass(
|
||||
exceptions.ExtensionError, exceptions.MopidyException))
|
||||
34
tests/ext_test.py
Normal file
34
tests/ext_test.py
Normal file
@ -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_classes_returns_an_empty_list(self):
|
||||
self.assertListEqual(self.ext.get_frontend_classes(), [])
|
||||
|
||||
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())
|
||||
@ -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
|
||||
|
||||
|
||||
@ -11,8 +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,
|
||||
'SPOTIFY_BITRATE': 160,
|
||||
@ -75,26 +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_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')})
|
||||
self.assertEqual(
|
||||
result['FRONTENDS'],
|
||||
'Must be a tuple. '
|
||||
"Remember the comma after single values: (u'value',)")
|
||||
|
||||
|
||||
class SettingsProxyTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
|
||||
Loading…
Reference in New Issue
Block a user