mpd: Use new config system

This commit is contained in:
Stein Magnus Jodal 2013-04-05 16:07:00 +02:00
parent 8a8a78e025
commit c452f0115a
12 changed files with 117 additions and 120 deletions

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

@ -42,6 +42,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)
@ -67,7 +68,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

@ -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

@ -23,12 +23,20 @@ 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

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]]),