From de21872d67165cd603eb8f67d02e25fc2db24e88 Mon Sep 17 00:00:00 2001 From: Alexandre Petitjean Date: Thu, 13 Feb 2014 10:49:56 +0100 Subject: [PATCH 1/6] #390 Adds basic proxy support for http source --- mopidy/audio/actor.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/mopidy/audio/actor.py b/mopidy/audio/actor.py index ca023125..5d12954c 100644 --- a/mopidy/audio/actor.py +++ b/mopidy/audio/actor.py @@ -102,6 +102,7 @@ class Audio(pykka.ThreadingActor): self._connect(playbin, 'about-to-finish', self._on_about_to_finish) self._connect(playbin, 'notify::source', self._on_new_source) + self._connect(playbin, 'source-setup', self._on_source_setup) self._playbin = playbin @@ -133,6 +134,22 @@ class Audio(pykka.ThreadingActor): self._appsrc = source + def _on_source_setup(self, element, source): + uri = element.get_property('uri') + if not uri or not uri.startswith('http://'): + return + + if self._config['proxy']['hostname']: + full_proxy = self._config['proxy']['hostname'] + if self._config['proxy']['port']: + full_proxy += ':' + str(self._config['proxy']['port']) + if self._config['proxy']['scheme']: + full_proxy = self._config['proxy']['scheme'] + "://" + full_proxy + + source.set_property('proxy', full_proxy) + source.set_property('proxy-id', self._config['proxy']['username']) + source.set_property('proxy-pw', self._config['proxy']['password']) + def _appsrc_on_need_data(self, appsrc, gst_length_hint): length_hint = utils.clocktime_to_millisecond(gst_length_hint) if self._appsrc_need_data_callback is not None: @@ -153,6 +170,7 @@ class Audio(pykka.ThreadingActor): def _teardown_playbin(self): self._disconnect(self._playbin, 'about-to-finish') self._disconnect(self._playbin, 'notify::source') + self._disconnect(self._playbin, 'source-setup') self._playbin.set_state(gst.STATE_NULL) def _setup_output(self): From 78149ab893ea396fdc88f76c9dbff4b484b3f7a4 Mon Sep 17 00:00:00 2001 From: Alexandre Petitjean Date: Thu, 13 Feb 2014 11:23:39 +0100 Subject: [PATCH 2/6] flake8 compliant --- mopidy/audio/actor.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/mopidy/audio/actor.py b/mopidy/audio/actor.py index 5d12954c..b985eac4 100644 --- a/mopidy/audio/actor.py +++ b/mopidy/audio/actor.py @@ -140,11 +140,21 @@ class Audio(pykka.ThreadingActor): return if self._config['proxy']['hostname']: - full_proxy = self._config['proxy']['hostname'] + + # default values + proxy_scheme = 'http' + proxy_port = 80 + if self._config['proxy']['port']: - full_proxy += ':' + str(self._config['proxy']['port']) + proxy_port = self._config['proxy']['port'] if self._config['proxy']['scheme']: - full_proxy = self._config['proxy']['scheme'] + "://" + full_proxy + proxy_scheme = self._config['proxy']['scheme'] + + full_proxy = "%s://%s:%s" % ( + proxy_scheme, + self._config['proxy']['hostname'], + str(proxy_port) + ) source.set_property('proxy', full_proxy) source.set_property('proxy-id', self._config['proxy']['username']) From 963d02fef59d9619b6bacef3e9aae5f2dc8aa298 Mon Sep 17 00:00:00 2001 From: Alexandre Petitjean Date: Thu, 13 Feb 2014 11:30:39 +0100 Subject: [PATCH 3/6] flake8 compliant --- mopidy/audio/actor.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mopidy/audio/actor.py b/mopidy/audio/actor.py index b985eac4..3a19b855 100644 --- a/mopidy/audio/actor.py +++ b/mopidy/audio/actor.py @@ -151,10 +151,10 @@ class Audio(pykka.ThreadingActor): proxy_scheme = self._config['proxy']['scheme'] full_proxy = "%s://%s:%s" % ( - proxy_scheme, - self._config['proxy']['hostname'], - str(proxy_port) - ) + proxy_scheme, + self._config['proxy']['hostname'], + str(proxy_port) + ) source.set_property('proxy', full_proxy) source.set_property('proxy-id', self._config['proxy']['username']) From 5e6e073cce32c14fdb3a8f58abe9daef58348659 Mon Sep 17 00:00:00 2001 From: Alexandre Petitjean Date: Mon, 17 Feb 2014 08:25:53 +0100 Subject: [PATCH 4/6] Merge branch 'develop' of https://github.com/AlexandrePTJ/mopidy into feature/gst_proxy --- docs/changelog.rst | 23 +++++++++----- mopidy/__init__.py | 2 +- mopidy/__main__.py | 68 ++++++++++++++++++++++++++++++---------- mopidy/config/keyring.py | 24 ++++++++------ mopidy/http/actor.py | 7 +++-- mopidy/mpd/actor.py | 7 +++-- mopidy/zeroconf.py | 2 +- tests/test_version.py | 6 ++-- 8 files changed, 96 insertions(+), 43 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index ea3af657..a63b7a29 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -5,17 +5,26 @@ Changelog This changelog is used to track all major changes to Mopidy. -v0.19.0 (UNRELEASED) -==================== - -- Nothing yet. - - -v0.18.2 (UNRELEASED) +v0.18.3 (2014-02-16) ==================== Bug fix release. +- Fix documentation build. + + +v0.18.2 (2014-02-16) +==================== + +Bug fix release. + +- We now log warnings for wrongly configured extensions, and clearly label them + in ``mopidy config``, but does no longer stop Mopidy from starting because of + misconfigured extensions. (Fixes: :issue:`682`) + +- Fix a crash in the server side WebSocket handler caused by connection + problems with clients. (Fixes: :issue:`428`, :issue:`571`) + - Fix the ``time_position`` field of the ``track_playback_ended`` event, which has been always 0 since v0.18.0. This made scrobbles by Mopidy-Scrobbler not be persisted by Last.fm, because Mopidy reported that you listened to 0 diff --git a/mopidy/__init__.py b/mopidy/__init__.py index 1367a219..c1646fa9 100644 --- a/mopidy/__init__.py +++ b/mopidy/__init__.py @@ -21,4 +21,4 @@ if (isinstance(pykka.__version__, basestring) warnings.filterwarnings('ignore', 'could not open display') -__version__ = '0.18.1' +__version__ = '0.18.3' diff --git a/mopidy/__main__.py b/mopidy/__main__.py index 05394bc2..b447e77d 100644 --- a/mopidy/__main__.py +++ b/mopidy/__main__.py @@ -74,20 +74,28 @@ def main(): log.setup_logging(config, verbosity_level, args.save_debug_log) - enabled_extensions = [] + extensions = { + 'validate': [], 'config': [], 'disabled': [], 'enabled': []} for extension in installed_extensions: if not ext.validate_extension(extension): config[extension.ext_name] = {'enabled': False} config_errors[extension.ext_name] = { 'enabled': 'extension disabled by self check.'} + extensions['validate'].append(extension) elif not config[extension.ext_name]['enabled']: config[extension.ext_name] = {'enabled': False} config_errors[extension.ext_name] = { 'enabled': 'extension disabled by user config.'} + extensions['disabled'].append(extension) + elif config_errors.get(extension.ext_name): + config[extension.ext_name]['enabled'] = False + config_errors[extension.ext_name]['enabled'] = ( + 'extension disabled due to config errors.') + extensions['config'].append(extension) else: - enabled_extensions.append(extension) + extensions['enabled'].append(extension) - log_extension_info(installed_extensions, enabled_extensions) + log_extension_info(installed_extensions, extensions['enabled']) # Config and deps commands are simply special cased for now. if args.command == config_cmd: @@ -96,22 +104,22 @@ def main(): elif args.command == deps_cmd: return args.command.run() - # Remove errors for extensions that are not enabled: - for extension in installed_extensions: - if extension not in enabled_extensions: - config_errors.pop(extension.ext_name, None) - check_config_errors(config_errors) + check_config_errors(config, config_errors, extensions) + + if not extensions['enabled']: + logger.error('No extension enabled, exiting...') + sys.exit(1) # Read-only config from here on, please. proxied_config = config_lib.Proxy(config) - if args.extension and args.extension not in enabled_extensions: + if args.extension and args.extension not in extensions['enabled']: logger.error( 'Unable to run command provided by disabled extension %s', args.extension.ext_name) return 1 - for extension in enabled_extensions: + for extension in extensions['enabled']: extension.setup(registry) # Anything that wants to exit after this point must use @@ -173,13 +181,39 @@ def log_extension_info(all_extensions, enabled_extensions): 'Disabled extensions: %s', ', '.join(disabled_names) or 'none') -def check_config_errors(errors): - if not errors: - return - for section in errors: - for key, msg in errors[section].items(): - logger.error('Config value %s/%s %s', section, key, msg) - sys.exit(1) +def check_config_errors(config, errors, extensions): + fatal_errors = [] + extension_names = {} + all_extension_names = set() + + for state in extensions: + extension_names[state] = set(e.ext_name for e in extensions[state]) + all_extension_names.update(extension_names[state]) + + for section in sorted(errors): + if not errors[section]: + continue + + if section not in all_extension_names: + logger.warning('Found fatal %s configuration errors:', section) + fatal_errors.append(section) + elif section in extension_names['config']: + del errors[section]['enabled'] + logger.warning('Found %s configuration errors, the extension ' + 'has been automatically disabled:', section) + else: + continue + + for field, msg in errors[section].items(): + logger.warning(' %s/%s %s', section, field, msg) + + if extensions['config']: + logger.warning('Please fix the extension configuration errors or ' + 'disable the extensions to silence these messages.') + + if fatal_errors: + logger.error('Please fix fatal configuration errors, exiting...') + sys.exit(1) if __name__ == '__main__': diff --git a/mopidy/config/keyring.py b/mopidy/config/keyring.py index 6800d2c4..4d251f52 100644 --- a/mopidy/config/keyring.py +++ b/mopidy/config/keyring.py @@ -19,20 +19,25 @@ else: EMPTY_STRING = '' +FETCH_ERROR = ( + 'Fetching passwords from your keyring failed. Any passwords ' + 'stored in the keyring will not be available.') + + def fetch(): if not dbus: - logger.debug('Fetching from keyring failed: dbus not installed.') + logger.debug('%s (dbus not installed)', FETCH_ERROR) return [] try: bus = dbus.SessionBus() except dbus.exceptions.DBusException as e: - logger.debug('Fetching from keyring failed: %s', e) + logger.debug('%s (%s)', FETCH_ERROR, e) return [] if not bus.name_has_owner('org.freedesktop.secrets'): logger.debug( - 'Fetching from keyring failed: secrets service not running.') + '%s (org.freedesktop.secrets service not running)', FETCH_ERROR) return [] service = _service(bus) @@ -47,7 +52,7 @@ def fetch(): items, prompt = service.Unlock(locked) if prompt != '/': _prompt(bus, prompt).Dismiss() - logger.debug('Fetching from keyring failed: keyring is locked.') + logger.debug('%s (Keyring is locked)', FETCH_ERROR) return [] result = [] @@ -65,19 +70,20 @@ def set(section, key, value): Indicates if storage failed or succeeded. """ if not dbus: - logger.debug('Saving %s/%s to keyring failed: dbus not installed.', + logger.debug('Saving %s/%s to keyring failed. (dbus not installed)', section, key) return False try: bus = dbus.SessionBus() except dbus.exceptions.DBusException as e: - logger.debug('Saving %s/%s to keyring failed: %s', section, key, e) + logger.debug('Saving %s/%s to keyring failed. (%s)', section, key, e) return False if not bus.name_has_owner('org.freedesktop.secrets'): logger.debug( - 'Saving %s/%s to keyring failed: secrets service not running.', + 'Saving %s/%s to keyring failed. ' + '(org.freedesktop.secrets service not running)', section, key) return False @@ -101,14 +107,14 @@ def set(section, key, value): item, prompt = collection.CreateItem(properties, secret, True) except dbus.exceptions.DBusException as e: # TODO: catch IsLocked errors etc. - logger.debug('Saving %s/%s to keyring failed: %s', section, key, e) + logger.debug('Saving %s/%s to keyring failed. (%s)', section, key, e) return False if prompt == '/': return True _prompt(bus, prompt).Dismiss() - logger.debug('Saving secret %s/%s failed: Keyring is locked', + logger.debug('Saving secret %s/%s failed. (Keyring is locked)', section, key) return False diff --git a/mopidy/http/actor.py b/mopidy/http/actor.py index e7b5cb66..c5787fec 100644 --- a/mopidy/http/actor.py +++ b/mopidy/http/actor.py @@ -100,10 +100,11 @@ class HttpFrontend(pykka.ThreadingActor, CoreListener): host=self.hostname, port=self.port) if self.zeroconf_service.publish(): - logger.info('Registered HTTP with Zeroconf as "%s"', - self.zeroconf_service.name) + logger.debug( + 'Registered HTTP with Zeroconf as "%s"', + self.zeroconf_service.name) else: - logger.info('Registering HTTP with Zeroconf failed.') + logger.debug('Registering HTTP with Zeroconf failed.') def on_stop(self): if self.zeroconf_service: diff --git a/mopidy/mpd/actor.py b/mopidy/mpd/actor.py index 20417a4d..684b4968 100644 --- a/mopidy/mpd/actor.py +++ b/mopidy/mpd/actor.py @@ -48,10 +48,11 @@ class MpdFrontend(pykka.ThreadingActor, CoreListener): host=self.hostname, port=self.port) if self.zeroconf_service.publish(): - logger.info('Registered MPD with Zeroconf as "%s"', - self.zeroconf_service.name) + logger.debug( + 'Registered MPD with Zeroconf as "%s"', + self.zeroconf_service.name) else: - logger.info('Registering MPD with Zeroconf failed.') + logger.debug('Registering MPD with Zeroconf failed.') def on_stop(self): if self.zeroconf_service: diff --git a/mopidy/zeroconf.py b/mopidy/zeroconf.py index e95b1792..1111975f 100644 --- a/mopidy/zeroconf.py +++ b/mopidy/zeroconf.py @@ -63,7 +63,7 @@ class Zeroconf(object): """ if _is_loopback_address(self.host): - logger.info( + logger.debug( 'Zeroconf publish on loopback interface is not supported.') return False diff --git a/tests/test_version.py b/tests/test_version.py index 23c93f01..b05f356e 100644 --- a/tests/test_version.py +++ b/tests/test_version.py @@ -43,5 +43,7 @@ class VersionTest(unittest.TestCase): 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('0.18.0')) - self.assertLess(SV('0.18.0'), SV(__version__)) - self.assertLess(SV(__version__), SV('0.18.2')) + self.assertLess(SV('0.18.0'), SV('0.18.1')) + self.assertLess(SV('0.18.1'), SV('0.18.2')) + self.assertLess(SV('0.18.2'), SV(__version__)) + self.assertLess(SV(__version__), SV('0.18.4')) From 6f06ec02c3c8a1f868793d40c1c4f6ae5dde2fb0 Mon Sep 17 00:00:00 2001 From: Alexandre Petitjean Date: Mon, 17 Feb 2014 09:42:17 +0100 Subject: [PATCH 5/6] Assign proxy when source need it (not only http) --- mopidy/audio/actor.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/mopidy/audio/actor.py b/mopidy/audio/actor.py index 3a19b855..0dba23eb 100644 --- a/mopidy/audio/actor.py +++ b/mopidy/audio/actor.py @@ -135,13 +135,8 @@ class Audio(pykka.ThreadingActor): self._appsrc = source def _on_source_setup(self, element, source): - uri = element.get_property('uri') - if not uri or not uri.startswith('http://'): - return + if hasattr(source.props, 'proxy') and self._config['proxy']['hostname']: - if self._config['proxy']['hostname']: - - # default values proxy_scheme = 'http' proxy_port = 80 From e269496c07050518c33210711279468f0d180b0a Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Fri, 9 May 2014 00:05:51 +0200 Subject: [PATCH 6/6] audio: Cleanup flake and some style issues --- mopidy/audio/actor.py | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/mopidy/audio/actor.py b/mopidy/audio/actor.py index 21744193..08c634e9 100644 --- a/mopidy/audio/actor.py +++ b/mopidy/audio/actor.py @@ -140,23 +140,18 @@ class Audio(pykka.ThreadingActor): self._appsrc = source def _on_source_setup(self, element, source): - if hasattr(source.props, 'proxy') and self._config['proxy']['hostname']: - - proxy_scheme = 'http' - proxy_port = 80 + scheme = 'http' + hostname = self._config['proxy']['hostname'] + port = 80 + if hasattr(source.props, 'proxy') and hostname: if self._config['proxy']['port']: - proxy_port = self._config['proxy']['port'] + port = self._config['proxy']['port'] if self._config['proxy']['scheme']: - proxy_scheme = self._config['proxy']['scheme'] + scheme = self._config['proxy']['scheme'] - full_proxy = "%s://%s:%s" % ( - proxy_scheme, - self._config['proxy']['hostname'], - str(proxy_port) - ) - - source.set_property('proxy', full_proxy) + proxy = "%s://%s:%d" % (scheme, hostname, port) + source.set_property('proxy', proxy) source.set_property('proxy-id', self._config['proxy']['username']) source.set_property('proxy-pw', self._config['proxy']['password'])