Merge pull request #395 from jodal/feature/use-new-config

Update all frontends and backends to use new config system
This commit is contained in:
Thomas Adamcik 2013-04-08 01:55:06 -07:00
commit 8bf9242e5b
46 changed files with 331 additions and 945 deletions

View File

@ -218,6 +218,4 @@ used at the same time without any danger of naming collisions.
Available settings
==================
.. automodule:: mopidy.settings
:synopsis: Available settings and their default values
:members:
.. note:: TODO: Document config values of the new config system

View File

@ -24,8 +24,3 @@ warnings.filterwarnings('ignore', 'could not open display')
__version__ = '0.13.0'
from mopidy import settings as default_settings_module
from mopidy.utils.settings import SettingsProxy
settings = SettingsProxy(default_settings_module)

View File

@ -28,7 +28,7 @@ sys.path.insert(
0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../')))
from mopidy import exceptions, settings
from mopidy import exceptions
from mopidy.audio import Audio
from mopidy.config import default_config, config_schemas
from mopidy.core import Core
@ -48,8 +48,10 @@ def main():
config_files = options.config.split(':')
config_overrides = options.overrides
extensions = [] # Make sure it is defined before the finally block
try:
extensions = [] # Make sure it is defined before the finally block
create_file_structures()
logging_config = load_config(config_files, config_overrides)
log.setup_logging(
logging_config, options.verbosity_level, options.save_debug_log)
@ -58,8 +60,7 @@ def main():
extensions = filter_enabled_extensions(raw_config, extensions)
config = validate_config(raw_config, config_schemas, extensions)
log.setup_log_levels(config)
check_old_folders()
setup_settings()
check_old_locations()
# Anything that wants to exit after this point must use
# mopidy.utils.process.exit_process as actors have been started.
@ -68,8 +69,6 @@ def main():
core = setup_core(audio, backends)
setup_frontends(config, extensions, core)
loop.run()
except exceptions.SettingsError as ex:
logger.error(ex.message)
except KeyboardInterrupt:
logger.info('Interrupted. Exiting...')
except Exception as ex:
@ -167,17 +166,20 @@ def show_config_callback(option, opt, value, parser):
sys.exit(0)
def check_old_folders():
# TODO: add old settings and pre extension storage locations?
old_settings_folder = os.path.expanduser('~/.mopidy')
def check_old_locations():
dot_mopidy_dir = path.expand_path('~/.mopidy')
if os.path.isdir(dot_mopidy_dir):
logger.warning(
'Old Mopidy dot dir found at %s. Please migrate your config to '
'the ini-file based config format. See release notes for further '
'instructions.', dot_mopidy_dir)
if not os.path.isdir(old_settings_folder):
return
logger.warning(
'Old settings folder found at %s, settings.py should be moved '
'to %s, any cache data should be deleted. See release notes for '
'further instructions.', old_settings_folder, path.SETTINGS_PATH)
old_settings_file = path.expand_path('$XDG_CONFIG_DIR/mopidy/settings.py')
if os.path.isfile(old_settings_file):
logger.warning(
'Old Mopidy settings file found at %s. Please migrate your '
'config to the ini-file based config format. See release notes '
'for further instructions.', old_settings_file)
def load_extensions():
@ -306,15 +308,10 @@ def validate_config(raw_config, schemas, extensions=None):
return config
def setup_settings():
path.get_or_create_folder(path.SETTINGS_PATH)
def create_file_structures():
path.get_or_create_folder(path.DATA_PATH)
path.get_or_create_file(path.SETTINGS_FILE)
try:
settings.validate()
except exceptions.SettingsError as ex:
logger.error(ex.message)
sys.exit(1)
path.get_or_create_folder(path.CONFIG_PATH)
path.get_or_create_file(path.CONFIG_FILE)
def setup_audio(config):

View File

@ -9,7 +9,6 @@ import logging
import pykka
from mopidy import settings
from mopidy.utils import process
from . import mixers, utils
@ -28,11 +27,14 @@ class Audio(pykka.ThreadingActor):
"""
Audio output through `GStreamer <http://gstreamer.freedesktop.org/>`_.
**Settings:**
**Default config:**
- :attr:`mopidy.settings.OUTPUT`
- :attr:`mopidy.settings.MIXER`
- :attr:`mopidy.settings.MIXER_TRACK`
.. code-block:: ini
[audio]
mixer = autoaudiomixer
mixer_track =
output = autoaudiosink
"""
#: The GStreamer state mapped to :class:`mopidy.audio.PlaybackState`
@ -41,6 +43,8 @@ class Audio(pykka.ThreadingActor):
def __init__(self, config):
super(Audio, self).__init__()
self._config = config
self._playbin = None
self._signal_ids = {} # {(element, event): signal_id}
@ -143,47 +147,51 @@ class Audio(pykka.ThreadingActor):
self._playbin.set_state(gst.STATE_NULL)
def _setup_output(self):
output_desc = self._config['audio']['output']
try:
output = gst.parse_bin_from_description(
settings.OUTPUT, ghost_unconnected_pads=True)
output_desc, ghost_unconnected_pads=True)
self._playbin.set_property('audio-sink', output)
logger.info('Audio output set to "%s"', settings.OUTPUT)
logger.info('Audio output set to "%s"', output_desc)
except gobject.GError as ex:
logger.error(
'Failed to create audio output "%s": %s', settings.OUTPUT, ex)
'Failed to create audio output "%s": %s', output_desc, ex)
process.exit_process()
def _setup_mixer(self):
if not settings.MIXER:
mixer_desc = self._config['audio']['mixer']
track_desc = self._config['audio']['mixer_track']
if mixer_desc is None:
logger.info('Not setting up audio mixer')
return
if settings.MIXER == 'software':
if mixer_desc == 'software':
self._software_mixing = True
logger.info('Audio mixer is using software mixing')
return
try:
mixerbin = gst.parse_bin_from_description(
settings.MIXER, ghost_unconnected_pads=False)
mixer_desc, ghost_unconnected_pads=False)
except gobject.GError as ex:
logger.warning(
'Failed to create audio mixer "%s": %s', settings.MIXER, ex)
'Failed to create audio mixer "%s": %s', mixer_desc, ex)
return
# We assume that the bin will contain a single mixer.
mixer = mixerbin.get_by_interface(b'GstMixer')
if not mixer:
logger.warning(
'Did not find any audio mixers in "%s"', settings.MIXER)
'Did not find any audio mixers in "%s"', mixer_desc)
return
if mixerbin.set_state(gst.STATE_READY) != gst.STATE_CHANGE_SUCCESS:
logger.warning(
'Setting audio mixer "%s" to READY failed', settings.MIXER)
'Setting audio mixer "%s" to READY failed', mixer_desc)
return
track = self._select_mixer_track(mixer, settings.MIXER_TRACK)
track = self._select_mixer_track(mixer, track_desc)
if not track:
logger.warning('Could not find usable audio mixer track')
return
@ -198,8 +206,9 @@ class Audio(pykka.ThreadingActor):
def _select_mixer_track(self, mixer, track_label):
# Ignore tracks without volumes, then look for track with
# label == settings.MIXER_TRACK, otherwise fallback to first usable
# track hoping the mixer gave them to us in a sensible order.
# label equal to the audio/mixer_track config value, otherwise fallback
# to first usable track hoping the mixer gave them to us in a sensible
# order.
usable_tracks = []
for track in mixer.list_tracks():

View File

@ -6,9 +6,9 @@ This is Mopidy's default mixer.
None
**Settings**
**Configuration**
If this wasn't the default, you would set :attr:`mopidy.settings.MIXER` to
If this wasn't the default, you would set the ``audio/mixer`` config value to
``autoaudiomixer`` to use this mixer.
"""

View File

@ -4,9 +4,9 @@
None
**Settings**
**Configuration**
Set :attr:`mopidy.settings.MIXER` to ``fakemixer`` to use this mixer.
Set the ``audio/mixer`` config value to ``fakemixer`` to use this mixer.
"""
from __future__ import unicode_literals

View File

@ -7,10 +7,10 @@ serial cable.
.. literalinclude:: ../../../../requirements/external_mixers.txt
**Settings**
**Configuration**
Set :attr:`mopidy.settings.MIXER` to ``nadmixer`` to use it. You probably also
needs to add some properties to the ``MIXER`` setting.
Set the ``audio/mixer`` config value to ``nadmixer`` to use it. You probably
also needs to add some properties to the ``audio/mixer`` config value.
Supported properties includes:
@ -34,15 +34,13 @@ Supported properties includes:
Configuration examples::
# Minimum configuration, if the amplifier is available at /dev/ttyUSB0
MIXER = u'nadmixer'
mixer = nadmixer
# Minimum configuration, if the amplifier is available elsewhere
MIXER = u'nadmixer port=/dev/ttyUSB3'
mixer = nadmixer port=/dev/ttyUSB3
# Full configuration
MIXER = (
u'nadmixer port=/dev/ttyUSB0 '
u'source=aux speakers-a=on speakers-b=off')
mixer = nadmixer port=/dev/ttyUSB0 source=aux speakers-a=on speakers-b=off
"""
from __future__ import unicode_literals
@ -132,7 +130,7 @@ class NadTalker(pykka.ThreadingActor):
calibrating the NAD amplifier's volume.
"""
# Serial link settings
# Serial link config
BAUDRATE = 115200
BYTESIZE = 8
PARITY = 'N'

View File

@ -16,6 +16,8 @@ class LocalBackend(pykka.ThreadingActor, base.Backend):
def __init__(self, config, audio):
super(LocalBackend, self).__init__()
self.config = config
self.library = LocalLibraryProvider(backend=self)
self.playback = base.BasePlaybackProvider(audio=audio, backend=self)
self.playlists = LocalPlaylistsProvider(backend=self)

View File

@ -2,7 +2,6 @@ from __future__ import unicode_literals
import logging
from mopidy import settings
from mopidy.backends import base
from mopidy.models import Album, SearchResult
@ -15,15 +14,17 @@ class LocalLibraryProvider(base.BaseLibraryProvider):
def __init__(self, *args, **kwargs):
super(LocalLibraryProvider, self).__init__(*args, **kwargs)
self._uri_mapping = {}
self._music_path = self.backend.config['local']['music_path']
self._playlist_path = self.backend.config['local']['playlist_path']
self._tag_cache_file = self.backend.config['local']['tag_cache_file']
self.refresh()
def refresh(self, uri=None):
tracks = parse_mpd_tag_cache(
settings.LOCAL_TAG_CACHE_FILE, settings.LOCAL_MUSIC_PATH)
tracks = parse_mpd_tag_cache(self._tag_cache_file, self._music_path)
logger.info(
'Loading tracks from %s using %s',
settings.LOCAL_MUSIC_PATH, settings.LOCAL_TAG_CACHE_FILE)
self._music_path, self._tag_cache_file)
for track in tracks:
self._uri_mapping[track.uri] = track

View File

@ -5,7 +5,6 @@ import logging
import os
import shutil
from mopidy import settings
from mopidy.backends import base, listener
from mopidy.models import Playlist
from mopidy.utils import formatting, path
@ -19,7 +18,8 @@ logger = logging.getLogger('mopidy.backends.local')
class LocalPlaylistsProvider(base.BasePlaylistsProvider):
def __init__(self, *args, **kwargs):
super(LocalPlaylistsProvider, self).__init__(*args, **kwargs)
self._path = settings.LOCAL_PLAYLIST_PATH
self._music_path = self.backend.config['local']['music_path']
self._playlist_path = self.backend.config['local']['playlist_path']
self.refresh()
def create(self, name):
@ -42,16 +42,16 @@ class LocalPlaylistsProvider(base.BasePlaylistsProvider):
return playlist
def refresh(self):
logger.info('Loading playlists from %s', self._path)
logger.info('Loading playlists from %s', self._playlist_path)
playlists = []
for m3u in glob.glob(os.path.join(self._path, '*.m3u')):
for m3u in glob.glob(os.path.join(self._playlist_path, '*.m3u')):
uri = path.path_to_uri(m3u)
name = os.path.splitext(os.path.basename(m3u))[0]
tracks = []
for track_uri in parse_m3u(m3u, settings.LOCAL_MUSIC_PATH):
for track_uri in parse_m3u(m3u, self._music_path):
try:
# TODO We must use core.library.lookup() to support tracks
# from other backends
@ -86,13 +86,13 @@ class LocalPlaylistsProvider(base.BasePlaylistsProvider):
def _get_m3u_path(self, name):
name = formatting.slugify(name)
file_path = os.path.join(self._path, name + '.m3u')
path.check_file_path_is_inside_base_dir(file_path, self._path)
file_path = os.path.join(self._playlist_path, name + '.m3u')
path.check_file_path_is_inside_base_dir(file_path, self._playlist_path)
return file_path
def _save_m3u(self, playlist):
file_path = path.uri_to_path(playlist.uri)
path.check_file_path_is_inside_base_dir(file_path, self._path)
path.check_file_path_is_inside_base_dir(file_path, self._playlist_path)
with open(file_path, 'w') as file_handle:
for track in playlist.tracks:
if track.uri.startswith('file://'):
@ -103,16 +103,18 @@ class LocalPlaylistsProvider(base.BasePlaylistsProvider):
def _delete_m3u(self, uri):
file_path = path.uri_to_path(uri)
path.check_file_path_is_inside_base_dir(file_path, self._path)
path.check_file_path_is_inside_base_dir(file_path, self._playlist_path)
if os.path.exists(file_path):
os.remove(file_path)
def _rename_m3u(self, playlist):
src_file_path = path.uri_to_path(playlist.uri)
path.check_file_path_is_inside_base_dir(src_file_path, self._path)
path.check_file_path_is_inside_base_dir(
src_file_path, self._playlist_path)
dst_file_path = self._get_m3u_path(playlist.name)
path.check_file_path_is_inside_base_dir(dst_file_path, self._path)
path.check_file_path_is_inside_base_dir(
dst_file_path, self._playlist_path)
shutil.move(src_file_path, dst_file_path)

View File

@ -23,9 +23,8 @@ protocols =
__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
installed.
This backend will handle streaming of URIs matching the ``stream/protocols``
config value, assuming the needed GStreamer plugins are installed.
**Issues**

View File

@ -5,7 +5,7 @@ import urlparse
import pykka
from mopidy import audio as audio_lib, settings
from mopidy import audio as audio_lib
from mopidy.backends import base
from mopidy.models import Track
@ -21,7 +21,7 @@ class StreamBackend(pykka.ThreadingActor, base.Backend):
self.playlists = None
self.uri_schemes = audio_lib.supported_uri_schemes(
settings.STREAM_PROTOCOLS)
config['stream']['protocols'])
# TODO: Should we consider letting lookup know how to expand common playlist

View File

@ -30,8 +30,7 @@ port = 6680
#
# Change this to have Mopidy serve e.g. files for your JavaScript client.
# "/mopidy" will continue to work as usual even if you change this setting.
#
static_path =
static_dir =
[logging.levels]
cherrypy = warning
@ -61,19 +60,19 @@ Setup
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`.
When it is enabled it starts a web server at the port specified by the
``http/port`` config value.
.. warning:: Security
As a simple security measure, the web server is by default only available
from localhost. To make it available from other computers, change
:attr:`mopidy.settings.HTTP_SERVER_HOSTNAME`. Before you do so, note that
the HTTP frontend does not feature any form of user authentication or
authorization. Anyone able to access the web server can use the full core
API of Mopidy. Thus, you probably only want to make the web server
available from your local network or place it behind a web proxy which
takes care or user authentication. You have been warned.
from localhost. To make it available from other computers, change the
``http/hostname`` config value. Before you do so, note that the HTTP
frontend does not feature any form of user authentication or authorization.
Anyone able to access the web server can use the full core API of Mopidy.
Thus, you probably only want to make the web server available from your
local network or place it behind a web proxy which takes care or user
authentication. You have been warned.
Using a web based Mopidy client
@ -81,10 +80,11 @@ Using a web based Mopidy client
The web server can also host any static files, for example the HTML, CSS,
JavaScript, and images needed for a web based Mopidy client. To host static
files, change :attr:`mopidy.settings.HTTP_SERVER_STATIC_DIR` to point to the
root directory of your web client, e.g.::
files, change the ``http/static_dir`` to point to the root directory of your
web client, e.g.::
HTTP_SERVER_STATIC_DIR = u'/home/alice/dev/the-client'
[http]
static_dir = /home/alice/dev/the-client
If the directory includes a file named ``index.html``, it will be served on the
root of Mopidy's web server.
@ -405,8 +405,7 @@ Example to get started with
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. Change the ``http/static_dir`` config value to point to your new directory.
4. Start/restart Mopidy.
@ -533,7 +532,7 @@ class Extension(ext.Extension):
schema = config.ExtensionConfigSchema()
schema['hostname'] = config.Hostname()
schema['port'] = config.Port()
schema['static_path'] = config.Path(optional=True)
schema['static_dir'] = config.Path(optional=True)
return schema
def validate_environment(self):

View File

@ -6,7 +6,7 @@ import os
import pykka
from mopidy import exceptions, models, settings
from mopidy import exceptions, models
from mopidy.core import CoreListener
try:
@ -25,6 +25,7 @@ logger = logging.getLogger('mopidy.frontends.http')
class HttpFrontend(pykka.ThreadingActor, CoreListener):
def __init__(self, config, core):
super(HttpFrontend, self).__init__()
self.config = config
self.core = core
self._setup_server()
self._setup_websocket_plugin()
@ -35,8 +36,8 @@ class HttpFrontend(pykka.ThreadingActor, CoreListener):
cherrypy.config.update({
'engine.autoreload_on': False,
'server.socket_host': (
settings.HTTP_SERVER_HOSTNAME.encode('utf-8')),
'server.socket_port': settings.HTTP_SERVER_PORT,
self.config['http']['hostname'].encode('utf-8')),
'server.socket_port': self.config['http']['port'],
})
def _setup_websocket_plugin(self):
@ -48,8 +49,8 @@ class HttpFrontend(pykka.ThreadingActor, CoreListener):
root.mopidy = MopidyResource()
root.mopidy.ws = ws.WebSocketResource(self.core)
if settings.HTTP_SERVER_STATIC_DIR:
static_dir = settings.HTTP_SERVER_STATIC_DIR
if self.config['http']['static_dir']:
static_dir = self.config['http']['static_dir']
else:
static_dir = os.path.join(os.path.dirname(__file__), 'data')
logger.debug('HTTP server will serve "%s" at /', static_dir)

View File

@ -5,7 +5,6 @@ import sys
import pykka
from mopidy import settings
from mopidy.core import CoreListener
from mopidy.frontends.mpd import session
from mopidy.utils import encoding, network, process
@ -16,17 +15,21 @@ logger = logging.getLogger('mopidy.frontends.mpd')
class MpdFrontend(pykka.ThreadingActor, CoreListener):
def __init__(self, config, core):
super(MpdFrontend, self).__init__()
hostname = network.format_hostname(settings.MPD_SERVER_HOSTNAME)
port = settings.MPD_SERVER_PORT
hostname = network.format_hostname(config['mpd']['hostname'])
port = config['mpd']['port']
# NOTE kwargs dict keys must be bytestrings to work on Python < 2.6.5
# See https://github.com/mopidy/mopidy/issues/302 for details.
try:
network.Server(
hostname, port,
protocol=session.MpdSession, protocol_kwargs={b'core': core},
max_connections=settings.MPD_SERVER_MAX_CONNECTIONS,
timeout=settings.MPD_SERVER_CONNECTION_TIMEOUT)
protocol=session.MpdSession,
protocol_kwargs={
b'config': config,
b'core': core,
},
max_connections=config['mpd']['max_connections'],
timeout=config['mpd']['connection_timeout'])
except IOError as error:
logger.error(
'MPD server startup failed: %s',

View File

@ -5,7 +5,6 @@ import re
import pykka
from mopidy import settings
from mopidy.frontends.mpd import exceptions, protocol
logger = logging.getLogger('mopidy.frontends.mpd.dispatcher')
@ -22,13 +21,15 @@ class MpdDispatcher(object):
_noidle = re.compile(r'^noidle$')
def __init__(self, session=None, core=None):
def __init__(self, session=None, config=None, core=None):
self.config = config
self.authenticated = False
self.command_list_receiving = False
self.command_list_ok = False
self.command_list = []
self.command_list_index = None
self.context = MpdContext(self, session=session, core=core)
self.context = MpdContext(
self, session=session, config=config, core=core)
def handle_request(self, request, current_command_list_index=None):
"""Dispatch incoming requests to the correct handler."""
@ -82,7 +83,7 @@ class MpdDispatcher(object):
def _authenticate_filter(self, request, response, filter_chain):
if self.authenticated:
return self._call_next_filter(request, response, filter_chain)
elif settings.MPD_SERVER_PASSWORD is None:
elif self.config['mpd']['password'] is None:
self.authenticated = True
return self._call_next_filter(request, response, filter_chain)
else:
@ -223,6 +224,9 @@ class MpdContext(object):
#: The current :class:`mopidy.frontends.mpd.MpdSession`.
session = None
#: The Mopidy configuration.
config = None
#: The Mopidy core API. An instance of :class:`mopidy.core.Core`.
core = None
@ -232,9 +236,10 @@ class MpdContext(object):
#: The subsytems that we want to be notified about in idle mode.
subscriptions = None
def __init__(self, dispatcher, session=None, core=None):
def __init__(self, dispatcher, session=None, config=None, core=None):
self.dispatcher = dispatcher
self.session = session
self.config = config
self.core = core
self.events = set()
self.subscriptions = set()

View File

@ -1,6 +1,5 @@
from __future__ import unicode_literals
from mopidy import settings
from mopidy.frontends.mpd.protocol import handle_request
from mopidy.frontends.mpd.exceptions import (
MpdPasswordError, MpdPermissionError)
@ -40,7 +39,7 @@ def password_(context, password):
This is used for authentication with the server. ``PASSWORD`` is
simply the plaintext password.
"""
if password == settings.MPD_SERVER_PASSWORD:
if password == context.config['mpd']['password']:
context.dispatcher.authenticated = True
else:
raise MpdPasswordError('incorrect password', command='password')

View File

@ -18,9 +18,10 @@ class MpdSession(network.LineProtocol):
encoding = protocol.ENCODING
delimiter = r'\r?\n'
def __init__(self, connection, core=None):
def __init__(self, connection, config=None, core=None):
super(MpdSession, self).__init__(connection)
self.dispatcher = dispatcher.MpdDispatcher(session=self, core=core)
self.dispatcher = dispatcher.MpdDispatcher(
session=self, config=config, core=core)
def on_start(self):
logger.info('New MPD connection from [%s]:%s', self.host, self.port)

View File

@ -5,7 +5,6 @@ import re
import shlex
import urllib
from mopidy import settings
from mopidy.frontends.mpd import protocol
from mopidy.frontends.mpd.exceptions import MpdArgError
from mopidy.models import TlTrack
@ -216,12 +215,14 @@ def query_from_mpd_search_format(mpd_query):
return query
def tracks_to_tag_cache_format(tracks):
def tracks_to_tag_cache_format(tracks, music_path):
"""
Format list of tracks for output to MPD tag cache
:param tracks: the tracks
:type tracks: list of :class:`mopidy.models.Track`
:param music_path: the path to the music dir
:type music_path: string
:rtype: list of lists of two-tuples
"""
result = [
@ -231,14 +232,15 @@ def tracks_to_tag_cache_format(tracks):
('info_end',)
]
tracks.sort(key=lambda t: t.uri)
_add_to_tag_cache(result, *tracks_to_directory_tree(tracks))
folders, files = tracks_to_directory_tree(tracks, music_path)
_add_to_tag_cache(result, folders, files, music_path)
return result
def _add_to_tag_cache(result, folders, files):
base_path = settings.LOCAL_MUSIC_PATH.encode('utf-8')
def _add_to_tag_cache(result, folders, files, music_path):
base_path = music_path.encode('utf-8')
for path, entry in folders.items():
for path, (entry_folders, entry_files) in folders.items():
try:
text_path = path.decode('utf-8')
except UnicodeDecodeError:
@ -247,7 +249,7 @@ def _add_to_tag_cache(result, folders, files):
result.append(('directory', text_path))
result.append(('mtime', get_mtime(os.path.join(base_path, path))))
result.append(('begin', name))
_add_to_tag_cache(result, *entry)
_add_to_tag_cache(result, entry_folders, entry_files, music_path)
result.append(('end', name))
result.append(('songList begin',))
@ -273,7 +275,7 @@ def _add_to_tag_cache(result, folders, files):
result.append(('songList end',))
def tracks_to_directory_tree(tracks):
def tracks_to_directory_tree(tracks, music_path):
directories = ({}, [])
for track in tracks:
@ -282,8 +284,7 @@ def tracks_to_directory_tree(tracks):
absolute_track_dir_path = os.path.dirname(uri_to_path(track.uri))
relative_track_dir_path = re.sub(
'^' + re.escape(settings.LOCAL_MUSIC_PATH), b'',
absolute_track_dir_path)
'^' + re.escape(music_path), b'', absolute_track_dir_path)
for part in split_path(relative_track_dir_path):
path = os.path.join(path, part)

View File

@ -32,8 +32,8 @@ An example of an MPRIS client is the `Ubuntu Sound Menu
Ubuntu Sound Menu. The package is named ``python-indicate`` in
Ubuntu/Debian.
- An ``.desktop`` file for Mopidy installed at the path set in
:attr:`mopidy.settings.DESKTOP_FILE`. See :ref:`install-desktop-file` for
- An ``.desktop`` file for Mopidy installed at the path set in the
``mpris/desktop_file`` config value. See :ref:`install-desktop-file` for
details.
**Default config**

View File

@ -4,7 +4,6 @@ import logging
import pykka
from mopidy import settings
from mopidy.core import CoreListener
from mopidy.frontends.mpris import objects
@ -20,13 +19,14 @@ except ImportError as import_error:
class MprisFrontend(pykka.ThreadingActor, CoreListener):
def __init__(self, config, core):
super(MprisFrontend, self).__init__()
self.config = config
self.core = core
self.indicate_server = None
self.mpris_object = None
def on_start(self):
try:
self.mpris_object = objects.MprisObject(self.core)
self.mpris_object = objects.MprisObject(self.config, self.core)
self._send_startup_notification()
except Exception as e:
logger.error('MPRIS frontend setup failed (%s)', e)
@ -53,7 +53,8 @@ class MprisFrontend(pykka.ThreadingActor, CoreListener):
logger.debug('Sending startup notification...')
self.indicate_server = indicate.Server()
self.indicate_server.set_type('music.mopidy')
self.indicate_server.set_desktop_file(settings.DESKTOP_FILE)
self.indicate_server.set_desktop_file(
self.config['mpris']['desktop_file'])
self.indicate_server.show()
logger.debug('Startup notification sent')

View File

@ -13,7 +13,6 @@ except ImportError as import_error:
from mopidy.exceptions import OptionalDependencyError
raise OptionalDependencyError(import_error)
from mopidy import settings
from mopidy.core import PlaybackState
from mopidy.utils.process import exit_process
@ -36,7 +35,8 @@ class MprisObject(dbus.service.Object):
properties = None
def __init__(self, core):
def __init__(self, config, core):
self.config = config
self.core = core
self.properties = {
ROOT_IFACE: self._get_root_iface_properties(),
@ -175,7 +175,8 @@ class MprisObject(dbus.service.Object):
### Root interface properties
def get_DesktopEntry(self):
return os.path.splitext(os.path.basename(settings.DESKTOP_FILE))[0]
return os.path.splitext(os.path.basename(
self.config['mpris']['desktop_file']))[0]
def get_SupportedUriSchemes(self):
return dbus.Array(self.core.uri_schemes.get(), signature='s')

View File

@ -5,7 +5,7 @@ import time
import pykka
from mopidy import exceptions, settings
from mopidy import exceptions
from mopidy.core import CoreListener
try:
@ -22,21 +22,17 @@ API_SECRET = '94d9a09c0cd5be955c4afaeaffcaefcd'
class ScrobblerFrontend(pykka.ThreadingActor, CoreListener):
def __init__(self, config, core):
super(ScrobblerFrontend, self).__init__()
self.config = config
self.lastfm = None
self.last_start_time = None
def on_start(self):
try:
username = settings.LASTFM_USERNAME
password_hash = pylast.md5(settings.LASTFM_PASSWORD)
self.lastfm = pylast.LastFMNetwork(
api_key=API_KEY, api_secret=API_SECRET,
username=username, password_hash=password_hash)
username=self.config['scrobbler']['username'],
password_hash=pylast.md5(self.config['scrobbler']['password']))
logger.info('Connected to Last.fm')
except exceptions.SettingsError as e:
logger.info('Last.fm scrobbler not started')
logger.debug('Last.fm settings error: %s', e)
self.stop()
except (pylast.NetworkError, pylast.MalformedResponseError,
pylast.WSError) as e:
logger.error('Error during Last.fm setup: %s', e)

View File

@ -34,7 +34,6 @@ import pygst
pygst.require('0.10')
import gst
from mopidy import settings
from mopidy.frontends.mpd import translator as mpd_translator
from mopidy.models import Track, Artist, Album
from mopidy.utils import log, path, versioning
@ -42,6 +41,7 @@ from mopidy.utils import log, path, versioning
def main():
options = parse_options()
config = {} # TODO Read config from new config system
log.setup_root_logger()
log.setup_console_logging(options.verbosity_level)
@ -57,9 +57,9 @@ def main():
logging.warning('Failed %s: %s', uri, error)
logging.debug('Debug info for %s: %s', uri, debug)
logging.info('Scanning %s', settings.LOCAL_MUSIC_PATH)
logging.info('Scanning %s', config['local']['music_path'])
scanner = Scanner(settings.LOCAL_MUSIC_PATH, store, debug)
scanner = Scanner(config['local']['music_path'], store, debug)
try:
scanner.start()
except KeyboardInterrupt:
@ -67,7 +67,8 @@ def main():
logging.info('Done scanning; writing tag cache...')
for row in mpd_translator.tracks_to_tag_cache_format(tracks):
for row in mpd_translator.tracks_to_tag_cache_format(
tracks, config['mpd']['music_path']):
if len(row) == 1:
print ('%s' % row).encode('utf-8')
else:

View File

@ -1,285 +0,0 @@
"""
All available settings and their default values.
.. warning::
Do *not* change settings directly in :mod:`mopidy.settings`. Instead, add a
file called ``~/.config/mopidy/settings.py`` and redefine settings there.
"""
from __future__ import unicode_literals
#: The log format used for informational logging.
#:
#: See http://docs.python.org/2/library/logging.html#formatter-objects for
#: details on the format.
CONSOLE_LOG_FORMAT = '%(levelname)-8s %(message)s'
#: The log format used for debug logging.
#:
#: See http://docs.python.org/library/logging.html#formatter-objects for
#: details on the format.
DEBUG_LOG_FORMAT = '%(levelname)-8s %(asctime)s' + \
' [%(process)d:%(threadName)s] %(name)s\n %(message)s'
#: The file to dump debug log data to when Mopidy is run with the
#: :option:`--save-debug-log` option.
#:
#: Default::
#:
#: DEBUG_LOG_FILENAME = u'mopidy.log'
DEBUG_LOG_FILENAME = 'mopidy.log'
#: Location of the Mopidy .desktop file.
#:
#: Used by :mod:`mopidy.frontends.mpris`.
#:
#: Default::
#:
#: DESKTOP_FILE = u'/usr/share/applications/mopidy.desktop'
DESKTOP_FILE = '/usr/share/applications/mopidy.desktop'
#: Which address Mopidy's HTTP server should bind to.
#:
#: Used by :mod:`mopidy.frontends.http`.
#:
#: Examples:
#:
#: ``127.0.0.1``
#: Listens only on the IPv4 loopback interface. Default.
#: ``::1``
#: Listens only on the IPv6 loopback interface.
#: ``0.0.0.0``
#: Listens on all IPv4 interfaces.
#: ``::``
#: Listens on all interfaces, both IPv4 and IPv6.
HTTP_SERVER_HOSTNAME = u'127.0.0.1'
#: Which TCP port Mopidy's HTTP server should listen to.
#:
#: Used by :mod:`mopidy.frontends.http`.
#:
#: Default: 6680
HTTP_SERVER_PORT = 6680
#: Which directory Mopidy's HTTP server should serve at ``/``.
#:
#: Change this to have Mopidy serve e.g. files for your JavaScript client.
#: ``/mopidy`` will continue to work as usual even if you change this setting.
#:
#: Used by :mod:`mopidy.frontends.http`.
#:
#: Default: None
HTTP_SERVER_STATIC_DIR = None
#: Your `Last.fm <http://www.last.fm/>`_ username.
#:
#: Used by :mod:`mopidy.frontends.lastfm`.
LASTFM_USERNAME = ''
#: Your `Last.fm <http://www.last.fm/>`_ password.
#:
#: Used by :mod:`mopidy.frontends.lastfm`.
LASTFM_PASSWORD = ''
#: Path to folder with local music.
#:
#: Used by :mod:`mopidy.backends.local`.
#:
#: Default::
#:
#: LOCAL_MUSIC_PATH = u'$XDG_MUSIC_DIR'
LOCAL_MUSIC_PATH = '$XDG_MUSIC_DIR'
#: Path to playlist folder with m3u files for local music.
#:
#: Used by :mod:`mopidy.backends.local`.
#:
#: Default::
#:
#: LOCAL_PLAYLIST_PATH = u'$XDG_DATA_DIR/mopidy/playlists'
LOCAL_PLAYLIST_PATH = '$XDG_DATA_DIR/mopidy/playlists'
#: Path to tag cache for local music.
#:
#: Used by :mod:`mopidy.backends.local`.
#:
#: Default::
#:
#: LOCAL_TAG_CACHE_FILE = u'$XDG_DATA_DIR/mopidy/tag_cache'
LOCAL_TAG_CACHE_FILE = '$XDG_DATA_DIR/mopidy/tag_cache'
#: Audio mixer to use.
#:
#: Expects a GStreamer mixer to use, typical values are:
#: ``alsamixer``, ``pulsemixer``, ``ossmixer``, and ``oss4mixer``.
#:
#: Setting this to :class:`None` turns off volume control. ``software``
#: can be used to force software mixing in the application.
#:
#: Default::
#:
#: MIXER = u'autoaudiomixer'
MIXER = 'autoaudiomixer'
#: Audio mixer track to use.
#:
#: Name of the mixer track to use. If this is not set we will try to find the
#: master output track. As an example, using ``alsamixer`` you would
#: typically set this to ``Master`` or ``PCM``.
#:
#: Default::
#:
#: MIXER_TRACK = None
MIXER_TRACK = None
#: Number of seconds an MPD client can stay inactive before the connection is
#: closed by the server.
#:
#: Used by :mod:`mopidy.frontends.mpd`.
#:
#: Default::
#:
#: MPD_SERVER_CONNECTION_TIMEOUT = 60
MPD_SERVER_CONNECTION_TIMEOUT = 60
#: Which address Mopidy's MPD server should bind to.
#:
#: Used by :mod:`mopidy.frontends.mpd`.
#:
#: Examples:
#:
#: ``127.0.0.1``
#: Listens only on the IPv4 loopback interface. Default.
#: ``::1``
#: Listens only on the IPv6 loopback interface.
#: ``0.0.0.0``
#: Listens on all IPv4 interfaces.
#: ``::``
#: Listens on all interfaces, both IPv4 and IPv6.
MPD_SERVER_HOSTNAME = '127.0.0.1'
#: Which TCP port Mopidy's MPD server should listen to.
#:
#: Used by :mod:`mopidy.frontends.mpd`.
#:
#: Default: 6600
MPD_SERVER_PORT = 6600
#: The password required for connecting to the MPD server.
#:
#: Used by :mod:`mopidy.frontends.mpd`.
#:
#: Default: :class:`None`, which means no password required.
MPD_SERVER_PASSWORD = None
#: The maximum number of concurrent connections the MPD server will accept.
#:
#: Used by :mod:`mopidy.frontends.mpd`.
#:
#: Default: 20
MPD_SERVER_MAX_CONNECTIONS = 20
#: Audio output to use.
#:
#: Expects a GStreamer sink. Typical values are ``autoaudiosink``,
#: ``alsasink``, ``osssink``, ``oss4sink``, ``pulsesink``, and ``shout2send``,
#: and additional arguments specific to each sink.
#:
#: Default::
#:
#: OUTPUT = u'autoaudiosink'
OUTPUT = 'autoaudiosink'
#: Path to the Spotify cache.
#:
#: Used by :mod:`mopidy.backends.spotify`.
#:
#: Default::
#:
#: SPOTIFY_CACHE_PATH = u'$XDG_CACHE_DIR/mopidy/spotify'
SPOTIFY_CACHE_PATH = '$XDG_CACHE_DIR/mopidy/spotify'
#: Your Spotify Premium username.
#:
#: Used by :mod:`mopidy.backends.spotify`.
SPOTIFY_USERNAME = ''
#: Your Spotify Premium password.
#:
#: Used by :mod:`mopidy.backends.spotify`.
SPOTIFY_PASSWORD = ''
#: Spotify preferred bitrate.
#:
#: Available values are 96, 160, and 320.
#:
#: Used by :mod:`mopidy.backends.spotify`.
#:
#: Default::
#:
#: SPOTIFY_BITRATE = 160
SPOTIFY_BITRATE = 160
#: Spotify proxy host.
#:
#: Used by :mod:`mopidy.backends.spotify`.
#:
#: Example::
#:
#: SPOTIFY_PROXY_HOST = u'protocol://host:port'
#:
#: Default::
#:
#: SPOTIFY_PROXY_HOST = None
SPOTIFY_PROXY_HOST = None
#: Spotify proxy username.
#:
#: Used by :mod:`mopidy.backends.spotify`.
#:
#: Default::
#:
#: SPOTIFY_PROXY_USERNAME = None
SPOTIFY_PROXY_USERNAME = None
#: Spotify proxy password.
#:
#: Used by :mod:`mopidy.backends.spotify`.
#:
#: Default::
#:
#: SPOTIFY_PROXY_PASSWORD = None
SPOTIFY_PROXY_PASSWORD = None
#: Max number of seconds to wait for Spotify operations to complete.
#:
#: Used by :mod:`mopidy.backends.spotify`.
#:
#: Default::
#:
#: SPOTIFY_TIMEOUT = 10
SPOTIFY_TIMEOUT = 10
#: Whitelist of URIs to support streaming from.
#:
#: Used by :mod:`mopidy.backends.stream`.
#:
#: Default::
#:
#: STREAM_PROTOCOLS = (
#: u'http',
#: u'https',
#: u'mms',
#: u'rtmp',
#: u'rtmps',
#: u'rtsp',
#: )
STREAM_PROTOCOLS = (
'http',
'https',
'mms',
'rtmp',
'rtmps',
'rtsp',
)

View File

@ -26,6 +26,8 @@ XDG_DIRS = {
'XDG_MUSIC_DIR': XDG_MUSIC_DIR,
}
DATA_PATH = os.path.join(unicode(XDG_DATA_DIR), 'mopidy')
CONFIG_PATH = os.path.join(unicode(XDG_CONFIG_DIR), 'mopidy')
CONFIG_FILE = os.path.join(unicode(CONFIG_PATH), 'mopidy.conf')
SETTINGS_PATH = os.path.join(unicode(XDG_CONFIG_DIR), 'mopidy')
SETTINGS_FILE = os.path.join(unicode(SETTINGS_PATH), 'settings.py')

View File

@ -1,173 +0,0 @@
# Absolute import needed to import ~/.config/mopidy/settings.py and not
# ourselves
from __future__ import absolute_import, unicode_literals
import copy
import getpass
import logging
import os
import pprint
import sys
from mopidy import exceptions
from mopidy.utils import formatting, path
logger = logging.getLogger('mopidy.utils.settings')
class SettingsProxy(object):
def __init__(self, default_settings_module):
self.default = self._get_settings_dict_from_module(
default_settings_module)
self.local = self._get_local_settings()
self.runtime = {}
def _get_local_settings(self):
if not os.path.isfile(path.SETTINGS_FILE):
return {}
sys.path.insert(0, path.SETTINGS_PATH)
# pylint: disable = F0401
import settings as local_settings_module
# pylint: enable = F0401
return self._get_settings_dict_from_module(local_settings_module)
def _get_settings_dict_from_module(self, module):
settings = filter(
lambda (key, value): self._is_setting(key),
module.__dict__.iteritems())
return dict(settings)
def _is_setting(self, name):
return name.isupper()
@property
def current(self):
current = copy.copy(self.default)
current.update(self.local)
current.update(self.runtime)
return current
def __getattr__(self, attr):
if not self._is_setting(attr):
return
current = self.current # bind locally to avoid copying+updates
if attr not in current:
raise exceptions.SettingsError('Setting "%s" is not set.' % attr)
value = current[attr]
if isinstance(value, basestring) and len(value) == 0:
raise exceptions.SettingsError('Setting "%s" is empty.' % attr)
if not value:
return value
if attr.endswith('_PATH') or attr.endswith('_FILE'):
value = path.expand_path(value)
return value
def __setattr__(self, attr, value):
if self._is_setting(attr):
self.runtime[attr] = value
else:
super(SettingsProxy, self).__setattr__(attr, value)
def validate(self):
if self.get_errors():
logger.error(
'Settings validation errors: %s',
formatting.indent(self.get_errors_as_string()))
raise exceptions.SettingsError('Settings validation failed.')
def _read_from_stdin(self, prompt):
if '_PASSWORD' in prompt:
return (
getpass.getpass(prompt)
.decode(sys.stdin.encoding, 'ignore'))
else:
sys.stdout.write(prompt)
return (
sys.stdin.readline().strip()
.decode(sys.stdin.encoding, 'ignore'))
def get_errors(self):
return validate_settings(self.default, self.local)
def get_errors_as_string(self):
lines = []
for (setting, error) in self.get_errors().iteritems():
lines.append('%s: %s' % (setting, error))
return '\n'.join(lines)
def validate_settings(defaults, settings):
"""
Checks the settings for both errors like misspellings and against a set of
rules for renamed settings, etc.
Returns mapping from setting names to associated errors.
:param defaults: Mopidy's default settings
:type defaults: dict
:param settings: the user's local settings
:type settings: dict
:rtype: dict
"""
errors = {}
changed = {
'DUMP_LOG_FILENAME': 'DEBUG_LOG_FILENAME',
'DUMP_LOG_FORMAT': 'DEBUG_LOG_FORMAT',
'GSTREAMER_AUDIO_SINK': 'OUTPUT',
'LOCAL_MUSIC_FOLDER': 'LOCAL_MUSIC_PATH',
'LOCAL_OUTPUT_OVERRIDE': 'OUTPUT',
'LOCAL_PLAYLIST_FOLDER': 'LOCAL_PLAYLIST_PATH',
'LOCAL_TAG_CACHE': 'LOCAL_TAG_CACHE_FILE',
'MIXER_ALSA_CONTROL': None,
'MIXER_EXT_PORT': None,
'MIXER_EXT_SPEAKERS_A': None,
'MIXER_EXT_SPEAKERS_B': None,
'MIXER_MAX_VOLUME': None,
'SERVER': None,
'SERVER_HOSTNAME': 'MPD_SERVER_HOSTNAME',
'SERVER_PORT': 'MPD_SERVER_PORT',
'SPOTIFY_HIGH_BITRATE': 'SPOTIFY_BITRATE',
'SPOTIFY_LIB_APPKEY': None,
'SPOTIFY_LIB_CACHE': 'SPOTIFY_CACHE_PATH',
}
must_be_iterable = [
'STREAM_PROTOCOLS',
]
for setting, value in settings.iteritems():
if setting in changed:
if changed[setting] is None:
errors[setting] = 'Deprecated setting. It may be removed.'
else:
errors[setting] = 'Deprecated setting. Use %s.' % (
changed[setting],)
elif setting == 'OUTPUTS':
errors[setting] = (
'Deprecated setting, please change to OUTPUT. OUTPUT expects '
'a GStreamer bin description string for your desired output.')
elif setting == 'SPOTIFY_BITRATE':
if value not in (96, 160, 320):
errors[setting] = (
'Unavailable Spotify bitrate. Available bitrates are 96, '
'160, and 320.')
elif setting.startswith('SHOUTCAST_OUTPUT_'):
errors[setting] = (
'Deprecated setting, please set the value via the GStreamer '
'bin in OUTPUT.')
elif setting in must_be_iterable and not hasattr(value, '__iter__'):
errors[setting] = (
'Must be a tuple. '
"Remember the comma after single values: (u'value',)")
elif setting not in defaults and not setting.startswith('CUSTOM_'):
errors[setting] = 'Unknown setting.'
return errors

View File

@ -8,11 +8,6 @@ if sys.version_info < (2, 7):
else:
import unittest # noqa
from mopidy import settings
# Nuke any local settings to ensure same test env all over
settings.local.clear()
def path_to_data_dir(name):
path = os.path.dirname(__file__)

View File

@ -6,7 +6,7 @@ import gst
import pykka
from mopidy import audio, settings
from mopidy import audio
from mopidy.utils.path import path_to_uri
from tests import unittest, path_to_data_dir
@ -14,14 +14,18 @@ from tests import unittest, path_to_data_dir
class AudioTest(unittest.TestCase):
def setUp(self):
settings.MIXER = 'fakemixer track_max_volume=65536'
settings.OUTPUT = 'fakesink'
config = {
'audio': {
'mixer': 'fakemixer track_max_volume=65536',
'mixer_track': None,
'output': 'fakesink',
}
}
self.song_uri = path_to_uri(path_to_data_dir('song1.wav'))
self.audio = audio.Audio.start(config=None).proxy()
self.audio = audio.Audio.start(config=config).proxy()
def tearDown(self):
pykka.ActorRegistry.stop_all()
settings.runtime.clear()
def prepare_uri(self, uri):
self.audio.prepare_change()
@ -59,8 +63,14 @@ class AudioTest(unittest.TestCase):
self.assertEqual(value, self.audio.get_volume().get())
def test_set_volume_with_mixer_max_below_100(self):
settings.MIXER = 'fakemixer track_max_volume=40'
self.audio = audio.Audio.start(config=None).proxy()
config = {
'audio': {
'mixer': 'fakemixer track_max_volume=40',
'mixer_track': None,
'output': 'fakesink',
}
}
self.audio = audio.Audio.start(config=config).proxy()
for value in range(0, 101):
self.assertTrue(self.audio.set_volume(value).get())

View File

@ -1,25 +1,17 @@
from __future__ import unicode_literals
import os
import shutil
import tempfile
import pykka
from mopidy import audio, core, settings
from mopidy import audio, core
from mopidy.models import Playlist
from tests import unittest, path_to_data_dir
from tests import unittest
class PlaylistsControllerTest(object):
config = {}
def setUp(self):
settings.LOCAL_PLAYLIST_PATH = tempfile.mkdtemp()
settings.LOCAL_TAG_CACHE_FILE = path_to_data_dir('library_tag_cache')
settings.LOCAL_MUSIC_PATH = path_to_data_dir('')
self.audio = audio.DummyAudio.start().proxy()
self.backend = self.backend_class.start(
config=self.config, audio=self.audio).proxy()
@ -28,11 +20,6 @@ class PlaylistsControllerTest(object):
def tearDown(self):
pykka.ActorRegistry.stop_all()
if os.path.exists(settings.LOCAL_PLAYLIST_PATH):
shutil.rmtree(settings.LOCAL_PLAYLIST_PATH)
settings.runtime.clear()
def test_create_returns_playlist_with_name_set(self):
playlist = self.core.playlists.create('test')
self.assertEqual(playlist.name, 'test')

View File

@ -1,4 +1,5 @@
from mopidy import settings
from __future__ import unicode_literals
from mopidy.backends.local import actor
from tests import unittest, path_to_data_dir
@ -7,12 +8,10 @@ from tests.backends.base import events
class LocalBackendEventsTest(events.BackendEventsTest, unittest.TestCase):
backend_class = actor.LocalBackend
# TODO: setup config
def setUp(self):
settings.LOCAL_TAG_CACHE_FILE = path_to_data_dir('empty_tag_cache')
super(LocalBackendEventsTest, self).setUp()
def tearDown(self):
super(LocalBackendEventsTest, self).tearDown()
settings.runtime.clear()
config = {
'local': {
'music_path': path_to_data_dir(''),
'playlist_path': '',
'tag_cache_file': path_to_data_dir('empty_tag_cache'),
}
}

View File

@ -1,6 +1,5 @@
from __future__ import unicode_literals
from mopidy import settings
from mopidy.backends.local import actor
from tests import unittest, path_to_data_dir
@ -9,15 +8,10 @@ from tests.backends.base.library import LibraryControllerTest
class LocalLibraryControllerTest(LibraryControllerTest, unittest.TestCase):
backend_class = actor.LocalBackend
# TODO: setup config
def setUp(self):
settings.LOCAL_TAG_CACHE_FILE = path_to_data_dir('library_tag_cache')
settings.LOCAL_MUSIC_PATH = path_to_data_dir('')
super(LocalLibraryControllerTest, self).setUp()
def tearDown(self):
settings.runtime.clear()
super(LocalLibraryControllerTest, self).tearDown()
config = {
'local': {
'music_path': path_to_data_dir(''),
'playlist_path': '',
'tag_cache_file': path_to_data_dir('library_tag_cache'),
}
}

View File

@ -1,6 +1,5 @@
from __future__ import unicode_literals
from mopidy import settings
from mopidy.backends.local import actor
from mopidy.core import PlaybackState
from mopidy.models import Track
@ -13,17 +12,15 @@ from tests.backends.local import generate_song
class LocalPlaybackControllerTest(PlaybackControllerTest, unittest.TestCase):
backend_class = actor.LocalBackend
config = {
'local': {
'music_path': path_to_data_dir(''),
'playlist_path': '',
'tag_cache_file': path_to_data_dir('empty_tag_cache'),
}
}
tracks = [
Track(uri=generate_song(i), length=4464) for i in range(1, 4)]
# TODO: setup config
def setUp(self):
settings.LOCAL_TAG_CACHE_FILE = path_to_data_dir('empty_tag_cache')
super(LocalPlaybackControllerTest, self).setUp()
def tearDown(self):
super(LocalPlaybackControllerTest, self).tearDown()
settings.runtime.clear()
def add_track(self, path):
uri = path_to_uri(path_to_data_dir(path))

View File

@ -1,8 +1,9 @@
from __future__ import unicode_literals
import os
import shutil
import tempfile
from mopidy import settings
from mopidy.backends.local import actor
from mopidy.models import Track
from mopidy.utils.path import path_to_uri
@ -17,25 +18,34 @@ class LocalPlaylistsControllerTest(
PlaylistsControllerTest, unittest.TestCase):
backend_class = actor.LocalBackend
# TODO: setup config
config = {
'local': {
'music_path': path_to_data_dir(''),
'tag_cache_file': path_to_data_dir('library_tag_cache'),
}
}
def setUp(self):
settings.LOCAL_TAG_CACHE_FILE = path_to_data_dir('empty_tag_cache')
self.config['local']['playlist_path'] = tempfile.mkdtemp()
self.playlist_path = self.config['local']['playlist_path']
super(LocalPlaylistsControllerTest, self).setUp()
def tearDown(self):
super(LocalPlaylistsControllerTest, self).tearDown()
settings.runtime.clear()
if os.path.exists(self.playlist_path):
shutil.rmtree(self.playlist_path)
def test_created_playlist_is_persisted(self):
path = os.path.join(settings.LOCAL_PLAYLIST_PATH, 'test.m3u')
path = os.path.join(self.playlist_path, 'test.m3u')
self.assertFalse(os.path.exists(path))
self.core.playlists.create('test')
self.assertTrue(os.path.exists(path))
def test_create_slugifies_playlist_name(self):
path = os.path.join(settings.LOCAL_PLAYLIST_PATH, 'test-foo-bar.m3u')
path = os.path.join(self.playlist_path, 'test-foo-bar.m3u')
self.assertFalse(os.path.exists(path))
playlist = self.core.playlists.create('test FOO baR')
@ -43,7 +53,7 @@ class LocalPlaylistsControllerTest(
self.assertTrue(os.path.exists(path))
def test_create_slugifies_names_which_tries_to_change_directory(self):
path = os.path.join(settings.LOCAL_PLAYLIST_PATH, 'test-foo-bar.m3u')
path = os.path.join(self.playlist_path, 'test-foo-bar.m3u')
self.assertFalse(os.path.exists(path))
playlist = self.core.playlists.create('../../test FOO baR')
@ -51,8 +61,8 @@ class LocalPlaylistsControllerTest(
self.assertTrue(os.path.exists(path))
def test_saved_playlist_is_persisted(self):
path1 = os.path.join(settings.LOCAL_PLAYLIST_PATH, 'test1.m3u')
path2 = os.path.join(settings.LOCAL_PLAYLIST_PATH, 'test2-foo-bar.m3u')
path1 = os.path.join(self.playlist_path, 'test1.m3u')
path2 = os.path.join(self.playlist_path, 'test2-foo-bar.m3u')
playlist = self.core.playlists.create('test1')
@ -67,7 +77,7 @@ class LocalPlaylistsControllerTest(
self.assertTrue(os.path.exists(path2))
def test_deleted_playlist_is_removed(self):
path = os.path.join(settings.LOCAL_PLAYLIST_PATH, 'test.m3u')
path = os.path.join(self.playlist_path, 'test.m3u')
self.assertFalse(os.path.exists(path))
playlist = self.core.playlists.create('test')
@ -90,7 +100,7 @@ class LocalPlaylistsControllerTest(
self.assertEqual(track_path, contents.strip())
def test_playlists_are_loaded_at_startup(self):
playlist_path = os.path.join(settings.LOCAL_PLAYLIST_PATH, 'test.m3u')
playlist_path = os.path.join(self.playlist_path, 'test.m3u')
track = Track(uri=path_to_uri(path_to_data_dir('uri2')))
playlist = self.core.playlists.create('test')

View File

@ -1,6 +1,5 @@
from __future__ import unicode_literals
from mopidy import settings
from mopidy.backends.local import actor
from mopidy.models import Track
@ -11,14 +10,12 @@ from tests.backends.local import generate_song
class LocalTracklistControllerTest(TracklistControllerTest, unittest.TestCase):
backend_class = actor.LocalBackend
config = {
'local': {
'music_path': path_to_data_dir(''),
'playlist_path': '',
'tag_cache_file': path_to_data_dir('empty_tag_cache'),
}
}
tracks = [
Track(uri=generate_song(i), length=4464) for i in range(1, 4)]
# TODO: setup config
def setUp(self):
settings.LOCAL_TAG_CACHE_FILE = path_to_data_dir('empty_tag_cache')
super(LocalTracklistControllerTest, self).setUp()
def tearDown(self):
super(LocalTracklistControllerTest, self).tearDown()
settings.runtime.clear()

View File

@ -1,3 +1,5 @@
from __future__ import unicode_literals
import json
try:
@ -24,7 +26,14 @@ from tests import unittest
@mock.patch('cherrypy.engine.publish')
class HttpEventsTest(unittest.TestCase):
def setUp(self):
self.http = actor.HttpFrontend(config=None, core=mock.Mock())
config = {
'http': {
'hostname': '127.0.0.1',
'port': 6680,
'static_dir': None,
}
}
self.http = actor.HttpFrontend(config=config, core=mock.Mock())
def test_track_playback_paused_is_broadcasted(self, publish):
publish.reset_mock()

View File

@ -13,9 +13,14 @@ from tests import unittest
class MpdDispatcherTest(unittest.TestCase):
def setUp(self):
config = {
'mpd': {
'password': None,
}
}
self.backend = dummy.create_dummy_backend_proxy()
self.core = core.Core.start(backends=[self.backend]).proxy()
self.dispatcher = MpdDispatcher()
self.dispatcher = MpdDispatcher(config=config)
def tearDown(self):
pykka.ActorRegistry.stop_all()

View File

@ -3,7 +3,7 @@ from __future__ import unicode_literals
import mock
import pykka
from mopidy import core, settings
from mopidy import core
from mopidy.backends import dummy
from mopidy.frontends.mpd import session
@ -23,18 +23,25 @@ class MockConnection(mock.Mock):
class BaseTestCase(unittest.TestCase):
def get_config(self):
return {
'mpd': {
'password': None,
}
}
def setUp(self):
self.backend = dummy.create_dummy_backend_proxy()
self.core = core.Core.start(backends=[self.backend]).proxy()
self.connection = MockConnection()
self.session = session.MpdSession(self.connection, core=self.core)
self.session = session.MpdSession(
self.connection, config=self.get_config(), core=self.core)
self.dispatcher = self.session.dispatcher
self.context = self.dispatcher.context
def tearDown(self):
pykka.ActorRegistry.stop_all()
settings.runtime.clear()
def sendRequest(self, request):
self.connection.response = []

View File

@ -1,63 +1,56 @@
from __future__ import unicode_literals
from mopidy import settings
from tests.frontends.mpd import protocol
class AuthenticationTest(protocol.BaseTestCase):
def test_authentication_with_valid_password_is_accepted(self):
settings.MPD_SERVER_PASSWORD = u'topsecret'
class AuthenticationActiveTest(protocol.BaseTestCase):
def get_config(self):
config = super(AuthenticationActiveTest, self).get_config()
config['mpd']['password'] = 'topsecret'
return config
def test_authentication_with_valid_password_is_accepted(self):
self.sendRequest('password "topsecret"')
self.assertTrue(self.dispatcher.authenticated)
self.assertInResponse('OK')
def test_authentication_with_invalid_password_is_not_accepted(self):
settings.MPD_SERVER_PASSWORD = u'topsecret'
self.sendRequest('password "secret"')
self.assertFalse(self.dispatcher.authenticated)
self.assertEqualResponse('ACK [3@0] {password} incorrect password')
def test_authentication_with_anything_when_password_check_turned_off(self):
settings.MPD_SERVER_PASSWORD = None
self.sendRequest('any request at all')
self.assertTrue(self.dispatcher.authenticated)
self.assertEqualResponse('ACK [5@0] {} unknown command "any"')
def test_anything_when_not_authenticated_should_fail(self):
settings.MPD_SERVER_PASSWORD = u'topsecret'
self.sendRequest('any request at all')
self.assertFalse(self.dispatcher.authenticated)
self.assertEqualResponse(
u'ACK [4@0] {any} you don\'t have permission for "any"')
def test_close_is_allowed_without_authentication(self):
settings.MPD_SERVER_PASSWORD = u'topsecret'
self.sendRequest('close')
self.assertFalse(self.dispatcher.authenticated)
def test_commands_is_allowed_without_authentication(self):
settings.MPD_SERVER_PASSWORD = u'topsecret'
self.sendRequest('commands')
self.assertFalse(self.dispatcher.authenticated)
self.assertInResponse('OK')
def test_notcommands_is_allowed_without_authentication(self):
settings.MPD_SERVER_PASSWORD = u'topsecret'
self.sendRequest('notcommands')
self.assertFalse(self.dispatcher.authenticated)
self.assertInResponse('OK')
def test_ping_is_allowed_without_authentication(self):
settings.MPD_SERVER_PASSWORD = u'topsecret'
self.sendRequest('ping')
self.assertFalse(self.dispatcher.authenticated)
self.assertInResponse('OK')
class AuthenticationInactiveTest(protocol.BaseTestCase):
def test_authentication_with_anything_when_password_check_turned_off(self):
self.sendRequest('any request at all')
self.assertTrue(self.dispatcher.authenticated)
self.assertEqualResponse('ACK [5@0] {} unknown command "any"')
def test_any_password_is_not_accepted_when_password_check_turned_off(self):
self.sendRequest('password "secret"')
self.assertEqualResponse('ACK [3@0] {password} incorrect password')

View File

@ -2,8 +2,6 @@ from __future__ import unicode_literals
from mock import patch
from mopidy import settings
from tests.frontends.mpd import protocol
@ -26,21 +24,6 @@ class ConnectionHandlerTest(protocol.BaseTestCase):
self.assertEqualResponse(
'ACK [4@0] {kill} you don\'t have permission for "kill"')
def test_valid_password_is_accepted(self):
settings.MPD_SERVER_PASSWORD = 'topsecret'
self.sendRequest('password "topsecret"')
self.assertEqualResponse('OK')
def test_invalid_password_is_not_accepted(self):
settings.MPD_SERVER_PASSWORD = 'topsecret'
self.sendRequest('password "secret"')
self.assertEqualResponse('ACK [3@0] {password} incorrect password')
def test_any_password_is_not_accepted_when_password_check_turned_off(self):
settings.MPD_SERVER_PASSWORD = None
self.sendRequest('password "secret"')
self.assertEqualResponse('ACK [3@0] {password} incorrect password')
def test_ping(self):
self.sendRequest('ping')
self.assertEqualResponse('OK')

View File

@ -1,7 +1,5 @@
from __future__ import unicode_literals
from mopidy import settings
from tests.frontends.mpd import protocol
@ -29,19 +27,6 @@ class ReflectionHandlerTest(protocol.BaseTestCase):
self.assertNotInResponse('command: sticker')
self.assertInResponse('OK')
def test_commands_show_less_if_auth_required_and_not_authed(self):
settings.MPD_SERVER_PASSWORD = u'secret'
self.sendRequest('commands')
# Not requiring auth
self.assertInResponse('command: close')
self.assertInResponse('command: commands')
self.assertInResponse('command: notcommands')
self.assertInResponse('command: password')
self.assertInResponse('command: ping')
# Requiring auth
self.assertNotInResponse('command: play')
self.assertNotInResponse('command: status')
def test_decoders(self):
self.sendRequest('decoders')
self.assertInResponse('OK')
@ -53,8 +38,35 @@ class ReflectionHandlerTest(protocol.BaseTestCase):
self.assertInResponse('command: kill')
self.assertInResponse('OK')
def test_tagtypes(self):
self.sendRequest('tagtypes')
self.assertInResponse('OK')
def test_urlhandlers(self):
self.sendRequest('urlhandlers')
self.assertInResponse('OK')
self.assertInResponse('handler: dummy')
class ReflectionWhenNotAuthedTest(protocol.BaseTestCase):
def get_config(self):
config = super(ReflectionWhenNotAuthedTest, self).get_config()
config['mpd']['password'] = 'topsecret'
return config
def test_commands_show_less_if_auth_required_and_not_authed(self):
self.sendRequest('commands')
# Not requiring auth
self.assertInResponse('command: close')
self.assertInResponse('command: commands')
self.assertInResponse('command: notcommands')
self.assertInResponse('command: password')
self.assertInResponse('command: ping')
# Requiring auth
self.assertNotInResponse('command: play')
self.assertNotInResponse('command: status')
def test_notcommands_returns_more_if_auth_required_and_not_authed(self):
settings.MPD_SERVER_PASSWORD = u'secret'
self.sendRequest('notcommands')
# Not requiring auth
self.assertNotInResponse('command: close')
@ -65,12 +77,3 @@ class ReflectionHandlerTest(protocol.BaseTestCase):
# Requiring auth
self.assertInResponse('command: play')
self.assertInResponse('command: status')
def test_tagtypes(self):
self.sendRequest('tagtypes')
self.assertInResponse('OK')
def test_urlhandlers(self):
self.sendRequest('urlhandlers')
self.assertInResponse('OK')
self.assertInResponse('handler: dummy')

View File

@ -3,7 +3,6 @@ from __future__ import unicode_literals
import datetime
import os
from mopidy import settings
from mopidy.utils.path import mtime, uri_to_path
from mopidy.frontends.mpd import translator, protocol
from mopidy.models import Album, Artist, TlTrack, Playlist, Track
@ -24,11 +23,10 @@ class TrackMpdFormatTest(unittest.TestCase):
)
def setUp(self):
settings.LOCAL_MUSIC_PATH = '/dir/subdir'
self.music_path = '/dir/subdir'
mtime.set_fake_time(1234567)
def tearDown(self):
settings.runtime.clear()
mtime.undo_fake()
def test_track_to_mpd_format_for_empty_track(self):
@ -137,15 +135,14 @@ class QueryFromMpdListFormatTest(unittest.TestCase):
class TracksToTagCacheFormatTest(unittest.TestCase):
def setUp(self):
settings.LOCAL_MUSIC_PATH = '/dir/subdir'
self.music_path = '/dir/subdir'
mtime.set_fake_time(1234567)
def tearDown(self):
settings.runtime.clear()
mtime.undo_fake()
def translate(self, track):
base_path = settings.LOCAL_MUSIC_PATH.encode('utf-8')
base_path = self.music_path.encode('utf-8')
result = dict(translator.track_to_mpd_format(track))
result['file'] = uri_to_path(result['file'])[len(base_path) + 1:]
result['key'] = os.path.basename(result['file'])
@ -177,11 +174,11 @@ class TracksToTagCacheFormatTest(unittest.TestCase):
self.fail("Couldn't find end %s in result" % directory)
def test_empty_tag_cache_has_header(self):
result = translator.tracks_to_tag_cache_format([])
result = translator.tracks_to_tag_cache_format([], self.music_path)
result = self.consume_headers(result)
def test_empty_tag_cache_has_song_list(self):
result = translator.tracks_to_tag_cache_format([])
result = translator.tracks_to_tag_cache_format([], self.music_path)
result = self.consume_headers(result)
song_list, result = self.consume_song_list(result)
@ -190,12 +187,12 @@ class TracksToTagCacheFormatTest(unittest.TestCase):
def test_tag_cache_has_header(self):
track = Track(uri='file:///dir/subdir/song.mp3')
result = translator.tracks_to_tag_cache_format([track])
result = translator.tracks_to_tag_cache_format([track], self.music_path)
result = self.consume_headers(result)
def test_tag_cache_has_song_list(self):
track = Track(uri='file:///dir/subdir/song.mp3')
result = translator.tracks_to_tag_cache_format([track])
result = translator.tracks_to_tag_cache_format([track], self.music_path)
result = self.consume_headers(result)
song_list, result = self.consume_song_list(result)
@ -205,7 +202,7 @@ class TracksToTagCacheFormatTest(unittest.TestCase):
def test_tag_cache_has_formated_track(self):
track = Track(uri='file:///dir/subdir/song.mp3')
formated = self.translate(track)
result = translator.tracks_to_tag_cache_format([track])
result = translator.tracks_to_tag_cache_format([track], self.music_path)
result = self.consume_headers(result)
song_list, result = self.consume_song_list(result)
@ -216,7 +213,7 @@ class TracksToTagCacheFormatTest(unittest.TestCase):
def test_tag_cache_has_formated_track_with_key_and_mtime(self):
track = Track(uri='file:///dir/subdir/song.mp3')
formated = self.translate(track)
result = translator.tracks_to_tag_cache_format([track])
result = translator.tracks_to_tag_cache_format([track], self.music_path)
result = self.consume_headers(result)
song_list, result = self.consume_song_list(result)
@ -227,7 +224,7 @@ class TracksToTagCacheFormatTest(unittest.TestCase):
def test_tag_cache_suports_directories(self):
track = Track(uri='file:///dir/subdir/folder/song.mp3')
formated = self.translate(track)
result = translator.tracks_to_tag_cache_format([track])
result = translator.tracks_to_tag_cache_format([track], self.music_path)
result = self.consume_headers(result)
folder, result = self.consume_directory(result)
@ -241,7 +238,7 @@ class TracksToTagCacheFormatTest(unittest.TestCase):
def test_tag_cache_diretory_header_is_right(self):
track = Track(uri='file:///dir/subdir/folder/sub/song.mp3')
result = translator.tracks_to_tag_cache_format([track])
result = translator.tracks_to_tag_cache_format([track], self.music_path)
result = self.consume_headers(result)
folder, result = self.consume_directory(result)
@ -253,7 +250,7 @@ class TracksToTagCacheFormatTest(unittest.TestCase):
def test_tag_cache_suports_sub_directories(self):
track = Track(uri='file:///dir/subdir/folder/sub/song.mp3')
formated = self.translate(track)
result = translator.tracks_to_tag_cache_format([track])
result = translator.tracks_to_tag_cache_format([track], self.music_path)
result = self.consume_headers(result)
@ -281,7 +278,7 @@ class TracksToTagCacheFormatTest(unittest.TestCase):
formated.extend(self.translate(tracks[0]))
formated.extend(self.translate(tracks[1]))
result = translator.tracks_to_tag_cache_format(tracks)
result = translator.tracks_to_tag_cache_format(tracks, self.music_path)
result = self.consume_headers(result)
song_list, result = self.consume_song_list(result)
@ -299,7 +296,7 @@ class TracksToTagCacheFormatTest(unittest.TestCase):
formated.append(self.translate(tracks[0]))
formated.append(self.translate(tracks[1]))
result = translator.tracks_to_tag_cache_format(tracks)
result = translator.tracks_to_tag_cache_format(tracks, self.music_path)
result = self.consume_headers(result)
folder, result = self.consume_directory(result)
@ -315,13 +312,10 @@ class TracksToTagCacheFormatTest(unittest.TestCase):
class TracksToDirectoryTreeTest(unittest.TestCase):
def setUp(self):
settings.LOCAL_MUSIC_PATH = '/root/'
def tearDown(self):
settings.runtime.clear()
self.music_path = '/root'
def test_no_tracks_gives_emtpy_tree(self):
tree = translator.tracks_to_directory_tree([])
tree = translator.tracks_to_directory_tree([], self.music_path)
self.assertEqual(tree, ({}, []))
def test_top_level_files(self):
@ -330,18 +324,18 @@ class TracksToDirectoryTreeTest(unittest.TestCase):
Track(uri='file:///root/file2.mp3'),
Track(uri='file:///root/file3.mp3'),
]
tree = translator.tracks_to_directory_tree(tracks)
tree = translator.tracks_to_directory_tree(tracks, self.music_path)
self.assertEqual(tree, ({}, tracks))
def test_single_file_in_subdir(self):
tracks = [Track(uri='file:///root/dir/file1.mp3')]
tree = translator.tracks_to_directory_tree(tracks)
tree = translator.tracks_to_directory_tree(tracks, self.music_path)
expected = ({'dir': ({}, tracks)}, [])
self.assertEqual(tree, expected)
def test_single_file_in_sub_subdir(self):
tracks = [Track(uri='file:///root/dir1/dir2/file1.mp3')]
tree = translator.tracks_to_directory_tree(tracks)
tree = translator.tracks_to_directory_tree(tracks, self.music_path)
expected = ({'dir1': ({'dir1/dir2': ({}, tracks)}, [])}, [])
self.assertEqual(tree, expected)
@ -353,7 +347,7 @@ class TracksToDirectoryTreeTest(unittest.TestCase):
Track(uri='file:///root/dir2/file4.mp3'),
Track(uri='file:///root/dir2/sub/file5.mp3'),
]
tree = translator.tracks_to_directory_tree(tracks)
tree = translator.tracks_to_directory_tree(tracks, self.music_path)
expected = (
{
'dir1': ({}, [tracks[1], tracks[2]]),

View File

@ -28,7 +28,7 @@ class PlayerInterfaceTest(unittest.TestCase):
objects.MprisObject._connect_to_dbus = mock.Mock()
self.backend = dummy.create_dummy_backend_proxy()
self.core = core.Core.start(backends=[self.backend]).proxy()
self.mpris = objects.MprisObject(core=self.core)
self.mpris = objects.MprisObject(config={}, core=self.core)
def tearDown(self):
pykka.ActorRegistry.stop_all()

View File

@ -25,7 +25,7 @@ class PlayerInterfaceTest(unittest.TestCase):
objects.MprisObject._connect_to_dbus = mock.Mock()
self.backend = dummy.create_dummy_backend_proxy()
self.core = core.Core.start(backends=[self.backend]).proxy()
self.mpris = objects.MprisObject(core=self.core)
self.mpris = objects.MprisObject(config={}, core=self.core)
foo = self.core.playlists.create('foo').get()
foo = foo.copy(last_modified=datetime.datetime(2012, 3, 1, 6, 0, 0))

View File

@ -5,7 +5,7 @@ import sys
import mock
import pykka
from mopidy import core, exceptions, settings
from mopidy import core, exceptions
from mopidy.backends import dummy
try:
@ -19,11 +19,17 @@ from tests import unittest
@unittest.skipUnless(sys.platform.startswith('linux'), 'requires Linux')
class RootInterfaceTest(unittest.TestCase):
def setUp(self):
config = {
'mpris': {
'desktop_file': '/tmp/foo.desktop',
}
}
objects.exit_process = mock.Mock()
objects.MprisObject._connect_to_dbus = mock.Mock()
self.backend = dummy.create_dummy_backend_proxy()
self.core = core.Core.start(backends=[self.backend]).proxy()
self.mpris = objects.MprisObject(core=self.core)
self.mpris = objects.MprisObject(config=config, core=self.core)
def tearDown(self):
pykka.ActorRegistry.stop_all()
@ -66,15 +72,9 @@ class RootInterfaceTest(unittest.TestCase):
result = self.mpris.Get(objects.ROOT_IFACE, 'Identity')
self.assertEquals(result, 'Mopidy')
def test_desktop_entry_is_mopidy(self):
result = self.mpris.Get(objects.ROOT_IFACE, 'DesktopEntry')
self.assertEquals(result, 'mopidy')
def test_desktop_entry_is_based_on_DESKTOP_FILE_setting(self):
settings.runtime['DESKTOP_FILE'] = '/tmp/foo.desktop'
result = self.mpris.Get(objects.ROOT_IFACE, 'DesktopEntry')
self.assertEquals(result, 'foo')
settings.runtime.clear()
def test_supported_uri_schemes_includes_backend_uri_schemes(self):
result = self.mpris.Get(objects.ROOT_IFACE, 'SupportedUriSchemes')

View File

@ -1,150 +0,0 @@
from __future__ import unicode_literals
import os
from mopidy import exceptions, settings
from mopidy.utils import settings as setting_utils
from tests import unittest
class ValidateSettingsTest(unittest.TestCase):
def setUp(self):
self.defaults = {
'MPD_SERVER_HOSTNAME': '::',
'MPD_SERVER_PORT': 6600,
'SPOTIFY_BITRATE': 160,
}
def test_no_errors_yields_empty_dict(self):
result = setting_utils.validate_settings(self.defaults, {})
self.assertEqual(result, {})
def test_unknown_setting_returns_error(self):
result = setting_utils.validate_settings(
self.defaults, {'MPD_SERVER_HOSTNMAE': '127.0.0.1'})
self.assertEqual(
result['MPD_SERVER_HOSTNMAE'], 'Unknown setting.')
def test_custom_settings_does_not_return_errors(self):
result = setting_utils.validate_settings(
self.defaults, {'CUSTOM_MYAPP_SETTING': 'foobar'})
self.assertNotIn('CUSTOM_MYAPP_SETTING', result)
def test_not_renamed_setting_returns_error(self):
result = setting_utils.validate_settings(
self.defaults, {'SERVER_HOSTNAME': '127.0.0.1'})
self.assertEqual(
result['SERVER_HOSTNAME'],
'Deprecated setting. Use MPD_SERVER_HOSTNAME.')
def test_unneeded_settings_returns_error(self):
result = setting_utils.validate_settings(
self.defaults, {'SPOTIFY_LIB_APPKEY': '/tmp/foo'})
self.assertEqual(
result['SPOTIFY_LIB_APPKEY'],
'Deprecated setting. It may be removed.')
def test_unavailable_bitrate_setting_returns_error(self):
result = setting_utils.validate_settings(
self.defaults, {'SPOTIFY_BITRATE': 50})
self.assertEqual(
result['SPOTIFY_BITRATE'],
'Unavailable Spotify bitrate. '
'Available bitrates are 96, 160, and 320.')
def test_two_errors_are_both_reported(self):
result = setting_utils.validate_settings(
self.defaults, {'FOO': '', 'BAR': ''})
self.assertEqual(len(result), 2)
class SettingsProxyTest(unittest.TestCase):
def setUp(self):
self.settings = setting_utils.SettingsProxy(settings)
self.settings.local.clear()
def test_set_and_get_attr(self):
self.settings.TEST = 'test'
self.assertEqual(self.settings.TEST, 'test')
def test_getattr_raises_error_on_missing_setting(self):
try:
self.settings.TEST
self.fail('Should raise exception')
except exceptions.SettingsError as e:
self.assertEqual('Setting "TEST" is not set.', e.message)
def test_getattr_raises_error_on_empty_setting(self):
self.settings.TEST = ''
try:
self.settings.TEST
self.fail('Should raise exception')
except exceptions.SettingsError as e:
self.assertEqual('Setting "TEST" is empty.', e.message)
def test_getattr_does_not_raise_error_if_setting_is_false(self):
self.settings.TEST = False
self.assertEqual(False, self.settings.TEST)
def test_getattr_does_not_raise_error_if_setting_is_none(self):
self.settings.TEST = None
self.assertEqual(None, self.settings.TEST)
def test_getattr_does_not_raise_error_if_setting_is_zero(self):
self.settings.TEST = 0
self.assertEqual(0, self.settings.TEST)
def test_setattr_updates_runtime_settings(self):
self.settings.TEST = 'test'
self.assertIn('TEST', self.settings.runtime)
def test_setattr_updates_runtime_with_value(self):
self.settings.TEST = 'test'
self.assertEqual(self.settings.runtime['TEST'], 'test')
def test_runtime_value_included_in_current(self):
self.settings.TEST = 'test'
self.assertEqual(self.settings.current['TEST'], 'test')
def test_value_ending_in_path_is_expanded(self):
self.settings.TEST_PATH = '~/test'
actual = self.settings.TEST_PATH
expected = os.path.expanduser('~/test')
self.assertEqual(actual, expected)
def test_value_ending_in_path_is_absolute(self):
self.settings.TEST_PATH = './test'
actual = self.settings.TEST_PATH
expected = os.path.abspath('./test')
self.assertEqual(actual, expected)
def test_value_ending_in_file_is_expanded(self):
self.settings.TEST_FILE = '~/test'
actual = self.settings.TEST_FILE
expected = os.path.expanduser('~/test')
self.assertEqual(actual, expected)
def test_value_ending_in_file_is_absolute(self):
self.settings.TEST_FILE = './test'
actual = self.settings.TEST_FILE
expected = os.path.abspath('./test')
self.assertEqual(actual, expected)
def test_value_not_ending_in_path_or_file_is_not_expanded(self):
self.settings.TEST = '~/test'
actual = self.settings.TEST
self.assertEqual(actual, '~/test')
def test_value_not_ending_in_path_or_file_is_not_absolute(self):
self.settings.TEST = './test'
actual = self.settings.TEST
self.assertEqual(actual, './test')
def test_value_ending_in_file_can_be_none(self):
self.settings.TEST_FILE = None
self.assertEqual(self.settings.TEST_FILE, None)
def test_value_ending_in_path_can_be_none(self):
self.settings.TEST_PATH = None
self.assertEqual(self.settings.TEST_PATH, None)