Merge branch 'develop' into feature/mpd-tokenized-requests

Conflicts:
	mopidy/mpd/protocol/music_db.py
	tests/mpd/protocol/test_music_db.py
This commit is contained in:
Thomas Adamcik 2014-01-24 19:38:56 +01:00
commit 3cec929bd7
12 changed files with 107 additions and 56 deletions

View File

@ -35,3 +35,5 @@
- Luke Giuliani <luke@giuliani.com.au>
- Colin Montgomerie <kiteflyingmonkey@gmail.com>
- Simon de Bakker <simon@simbits.nl>
- Arnaud Barisain-Monrose <abarisain@gmail.com>
- nathanharper <nathan.sam.harper@gmail.com>

View File

@ -4,7 +4,34 @@ Changelog
This changelog is used to track all major changes to Mopidy.
v0.18.0 (UNRELEASED)
v0.19.0 (unreleased)
====================
**MPD**
- Minor refactor of context such that it stores password instead of config.
(Fixes: :issue:`646`)
**Windows**
- Network and signal handling has been updated to play nice on windows systems.
v0.18.1 (2014-01-23)
====================
Bug fix release.
- Disable extension instead of crashing if a dependency has the wrong
version. (Fixes: :issue:`657`)
- Make logging work to both console, debug log file, and any custom logging
setup from :confval:`logging/config_file` at the same time. (Fixes:
:issue:`661`)
v0.18.0 (2014-01-19)
====================
The focus of 0.18 have been on two fronts: the local library and browsing.
@ -21,8 +48,8 @@ the local and Spotify backends. It is also used by the new Mopidy-Dirble
extension to provide you with a directory of Internet radio stations from all
over the world.
Since the release of 0.17, we've closed or merged 47 issues and pull requests
through about 270 commits by :ref:`11 people <authors>`, including six new
Since the release of 0.17, we've closed or merged 49 issues and pull requests
through about 285 commits by :ref:`11 people <authors>`, including six new
guys. Thanks to everyone that has contributed!
**Core API**
@ -1831,9 +1858,8 @@ to this problem.
- MPD frontend:
- Add support for password authentication. See
:attr:`mopidy.settings.MPD_SERVER_PASSWORD` and
:ref:`use-mpd-on-a-network` for details on how to use it. (Fixes:
:issue:`41`)
:attr:`mopidy.settings.MPD_SERVER_PASSWORD` for details on how to use it.
(Fixes: :issue:`41`)
- Support ``setvol 50`` without quotes around the argument. Fixes volume
control in Droid MPD.

View File

@ -21,4 +21,4 @@ if (isinstance(pykka.__version__, basestring)
warnings.filterwarnings('ignore', 'could not open display')
__version__ = '0.18.0'
__version__ = '0.18.1'

View File

@ -37,7 +37,9 @@ def main():
logger.info('Starting Mopidy %s', versioning.get_version())
signal.signal(signal.SIGTERM, process.exit_handler)
signal.signal(signal.SIGUSR1, pykka.debug.log_thread_tracebacks)
# Windows does not have signal.SIGUSR1
if hasattr(signal, 'SIGUSR1'):
signal.signal(signal.SIGUSR1, pykka.debug.log_thread_tracebacks)
try:
registry = ext.Registry()
@ -70,8 +72,7 @@ def main():
if args.verbosity_level:
verbosity_level += args.verbosity_level
log.setup_logging(
config, installed_extensions, verbosity_level, args.save_debug_log)
log.setup_logging(config, verbosity_level, args.save_debug_log)
enabled_extensions = []
for extension in installed_extensions:

View File

@ -222,6 +222,12 @@ def validate_extension(extension):
'Disabled extension %s: Dependency %s not found',
extension.ext_name, ex)
return False
except pkg_resources.VersionConflict as ex:
found, required = ex.args
logger.info(
'Disabled extension %s: %s required, but found %s at %s',
extension.ext_name, required, found, found.location)
return False
try:
extension.validate_environment()

View File

@ -66,12 +66,15 @@ class _BrowseCache(object):
for i in reversed(range(len(parts))):
directory = '/'.join(parts[:i+1])
uri = translator.path_to_local_directory_uri(directory)
# First dir we process is our parent
if not parent_uri:
parent_uri = uri
# We found ourselves and we exist, done.
if uri in self._cache:
if child:
self._cache[uri][child.uri] = child
break
# Initialize ourselves, store child if present, and add

View File

@ -218,8 +218,8 @@ class MpdContext(object):
#: The current :class:`mopidy.mpd.MpdSession`.
session = None
#: The Mopidy configuration.
config = None
#: The MPD password
password = None
#: The Mopidy core API. An instance of :class:`mopidy.core.Core`.
core = None
@ -235,7 +235,8 @@ class MpdContext(object):
def __init__(self, dispatcher, session=None, config=None, core=None):
self.dispatcher = dispatcher
self.session = session
self.config = config
if config is not None:
self.password = config['mpd']['password']
self.core = core
self.events = set()
self.subscriptions = set()

View File

@ -37,7 +37,7 @@ def password(context, password):
This is used for authentication with the server. ``PASSWORD`` is
simply the plaintext password.
"""
if password == context.config['mpd']['password']:
if password == context.password:
context.dispatcher.authenticated = True
else:
raise exceptions.MpdPasswordError('incorrect password')

View File

@ -27,23 +27,23 @@ _delayed_handler = DelayedHandler()
def bootstrap_delayed_logging():
root = logging.getLogger('')
root.setLevel(logging.DEBUG)
root.setLevel(logging.NOTSET)
root.addHandler(_delayed_handler)
def setup_logging(config, extensions, verbosity_level, save_debug_log):
setup_log_levels(config)
setup_console_logging(config, extensions, verbosity_level)
if save_debug_log:
setup_debug_logging_to_file(config, extensions)
def setup_logging(config, verbosity_level, save_debug_log):
logging.captureWarnings(True)
if config['logging']['config_file']:
# Logging config from file must be read before other handlers are
# added. If not, the other handlers will have no effect.
logging.config.fileConfig(config['logging']['config_file'])
setup_log_levels(config)
setup_console_logging(config, verbosity_level)
if save_debug_log:
setup_debug_logging_to_file(config)
_delayed_handler.release()
@ -61,46 +61,43 @@ LOG_LEVELS = {
}
def setup_console_logging(config, extensions, verbosity_level):
class VerbosityFilter(logging.Filter):
def __init__(self, verbosity_level):
self.verbosity_level = verbosity_level
def filter(self, record):
if record.name.startswith('mopidy'):
required_log_level = LOG_LEVELS[self.verbosity_level]['mopidy']
else:
required_log_level = LOG_LEVELS[self.verbosity_level]['root']
return record.levelno >= required_log_level
def setup_console_logging(config, verbosity_level):
if verbosity_level < min(LOG_LEVELS.keys()):
verbosity_level = min(LOG_LEVELS.keys())
if verbosity_level > max(LOG_LEVELS.keys()):
verbosity_level = max(LOG_LEVELS.keys())
verbosity_filter = VerbosityFilter(verbosity_level)
if verbosity_level < 1:
log_format = config['logging']['console_format']
else:
log_format = config['logging']['debug_format']
formatter = logging.Formatter(log_format)
root_handler = logging.StreamHandler()
root_handler.setFormatter(formatter)
root_handler.setLevel(LOG_LEVELS[verbosity_level]['root'])
logging.getLogger('').addHandler(root_handler)
handler = logging.StreamHandler()
handler.addFilter(verbosity_filter)
handler.setFormatter(formatter)
mopidy_handler = logging.StreamHandler()
mopidy_handler.setFormatter(formatter)
mopidy_handler.setLevel(LOG_LEVELS[verbosity_level]['mopidy'])
add_mopidy_handler(extensions, mopidy_handler)
logging.getLogger('').addHandler(handler)
def setup_debug_logging_to_file(config, extensions):
def setup_debug_logging_to_file(config):
formatter = logging.Formatter(config['logging']['debug_format'])
handler = logging.handlers.RotatingFileHandler(
config['logging']['debug_file'], maxBytes=10485760, backupCount=3)
handler.setFormatter(formatter)
logging.getLogger('').addHandler(handler)
# We must add our handler explicitly, since the mopidy* handlers don't
# propagate to the root handler.
add_mopidy_handler(extensions, handler)
def add_mopidy_handler(extensions, handler):
names = ['mopidy_%s' % ext.ext_name for ext in extensions]
names.append('mopidy')
for name in names:
logger = logging.getLogger(name)
logger.propagate = False
logger.addHandler(handler)

View File

@ -5,6 +5,7 @@ import gobject
import logging
import re
import socket
import sys
import threading
import pykka
@ -43,7 +44,12 @@ def create_socket():
if has_ipv6:
sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
# Explicitly configure socket to work for both IPv4 and IPv6
sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
if hasattr(socket, 'IPPROTO_IPV6'):
sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
elif sys.platform == 'win32': # also match 64bit windows.
# Python 2.7 on windows does not have the IPPROTO_IPV6 constant
# Use values extracted from Windows Vista/7/8's header
sock.setsockopt(41, 27, 0)
else:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

View File

@ -7,20 +7,27 @@ from mopidy.models import Ref
class BrowseCacheTest(unittest.TestCase):
maxDiff = None
def setUp(self):
self.uris = [b'local:track:foo/bar/song1',
b'local:track:foo/bar/song2',
b'local:track:foo/song3']
self.uris = ['local:track:foo/bar/song1',
'local:track:foo/bar/song2',
'local:track:foo/baz/song3',
'local:track:foo/song4',
'local:track:song5']
self.cache = json._BrowseCache(self.uris)
def test_lookup_root(self):
expected = [Ref.directory(uri='local:directory:foo', name='foo')]
expected = [Ref.directory(uri='local:directory:foo', name='foo'),
Ref.track(uri='local:track:song5', name='song5')]
self.assertEqual(expected, self.cache.lookup('local:directory'))
def test_lookup_foo(self):
expected = [Ref.directory(uri='local:directory:foo/bar', name='bar'),
Ref.track(uri=self.uris[2], name='song3')]
self.assertEqual(expected, self.cache.lookup('local:directory:foo'))
Ref.directory(uri='local:directory:foo/baz', name='baz'),
Ref.track(uri=self.uris[3], name='song4')]
result = self.cache.lookup('local:directory:foo')
self.assertEqual(expected, result)
def test_lookup_foo_bar(self):
expected = [Ref.track(uri=self.uris[0], name='song1'),
@ -29,4 +36,5 @@ class BrowseCacheTest(unittest.TestCase):
expected, self.cache.lookup('local:directory:foo/bar'))
def test_lookup_foo_baz(self):
self.assertEqual([], self.cache.lookup('local:directory:foo/baz'))
result = self.cache.lookup('local:directory:foo/unknown')
self.assertEqual([], result)

View File

@ -42,5 +42,6 @@ class VersionTest(unittest.TestCase):
self.assertLess(SV('0.14.2'), SV('0.15.0'))
self.assertLess(SV('0.15.0'), SV('0.16.0'))
self.assertLess(SV('0.16.0'), SV('0.17.0'))
self.assertLess(SV('0.17.0'), SV(__version__))
self.assertLess(SV(__version__), SV('0.18.1'))
self.assertLess(SV('0.17.0'), SV('0.18.0'))
self.assertLess(SV('0.18.0'), SV(__version__))
self.assertLess(SV(__version__), SV('0.18.2'))