From ae49c4d113085b3032b43c225ad3d9ea0c26734a Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Wed, 23 Jul 2014 17:35:55 +0200 Subject: [PATCH 01/29] http: Add missing string interpolation placeholder --- docs/changelog.rst | 4 ++++ mopidy/http/handlers.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index c461db63..7a218f98 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -13,6 +13,10 @@ v0.19.1 (UNRELEASED) Mopidy continue to work on Debian/Raspbian stable, where Tornado 2.3 is the newest version available. +**HTTP** + +- Add missing string interpolation placeholder. + **Development** - ``mopidy --version`` and :meth:`mopidy.core.Core.get_version` now returns the diff --git a/mopidy/http/handlers.py b/mopidy/http/handlers.py index 4ea2e97d..f35b5c7c 100644 --- a/mopidy/http/handlers.py +++ b/mopidy/http/handlers.py @@ -144,7 +144,7 @@ class JsonRpcHandler(tornado.web.RequestHandler): 'Sent RPC message to %s: %r', self.request.remote_ip, response) except Exception as e: - logger.error('HTTP JSON-RPC request error:', e) + logger.error('HTTP JSON-RPC request error: %s', e) self.write_error(500) def set_extra_headers(self): From d7cbac5a44689e494c65226f24c147b801c20093 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Wed, 23 Jul 2014 18:13:14 +0200 Subject: [PATCH 02/29] docs: Update changelog --- docs/changelog.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 7a218f98..f28bd8e9 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,9 +4,11 @@ Changelog This changelog is used to track all major changes to Mopidy. -v0.19.1 (UNRELEASED) +v0.19.1 (2014-07-23) ==================== +Bug fix release. + **Dependencies** - Mopidy now requires Tornado >= 2.3, instead of >= 3.1. This should make From 19dae1057549ee7c9e2c5e59c1b570a20cbc863e Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Wed, 23 Jul 2014 18:14:00 +0200 Subject: [PATCH 03/29] Bump version to 0.19.1 --- mopidy/__init__.py | 2 +- tests/test_version.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/mopidy/__init__.py b/mopidy/__init__.py index 6018a2ca..6ac5f6c0 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.19.0' +__version__ = '0.19.1' diff --git a/tests/test_version.py b/tests/test_version.py index c166922c..c596defd 100644 --- a/tests/test_version.py +++ b/tests/test_version.py @@ -46,5 +46,6 @@ class VersionTest(unittest.TestCase): 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('0.18.3')) - self.assertLess(SV('0.18.3'), SV(__version__)) - self.assertLess(SV(__version__), SV('0.19.1')) + self.assertLess(SV('0.18.3'), SV('0.19.0')) + self.assertLess(SV('0.19.0'), SV(__version__)) + self.assertLess(SV(__version__), SV('0.19.2')) From 0463b14a6a2c43609aa37486d1bb4155b1c5f670 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Thu, 24 Jul 2014 14:11:17 +0200 Subject: [PATCH 04/29] Revert "Remove 'mad' from 'mopidy deps' listing" This reverts commit cb6e19c039d2a49be235a34dcf05236b3f24625a. The "mad" element is the only way to decode MP3 with GStreamer 0.10 on OS X. --- mopidy/utils/deps.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mopidy/utils/deps.py b/mopidy/utils/deps.py index 46feb711..efc831e4 100644 --- a/mopidy/utils/deps.py +++ b/mopidy/utils/deps.py @@ -155,6 +155,7 @@ def _gstreamer_check_elements(): # MP3 encoding and decoding 'mp3parse', + 'mad', 'id3demux', 'id3v2mux', 'lame', From 6ab2ef85d4554c8dabb6ba08390ad6cf2e34c61e Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Thu, 24 Jul 2014 14:16:16 +0200 Subject: [PATCH 05/29] deps: Add flump3dec (and mpg123audiodec) to Gst element list --- mopidy/utils/deps.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/mopidy/utils/deps.py b/mopidy/utils/deps.py index efc831e4..b533a18f 100644 --- a/mopidy/utils/deps.py +++ b/mopidy/utils/deps.py @@ -154,11 +154,16 @@ def _gstreamer_check_elements(): 'pulsesink', # MP3 encoding and decoding - 'mp3parse', - 'mad', + # + # One of flump3dec, mad, and mpg123audiodec is required for MP3 + # playback. + 'flump3dec', 'id3demux', 'id3v2mux', 'lame', + 'mad', + 'mp3parse', + # 'mpg123audiodec', # Only available in GStreamer 1.x # Ogg Vorbis encoding and decoding 'vorbisdec', From c48103f1d2794dd2335b6cfa5b7da994b79b274e Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Thu, 24 Jul 2014 14:16:49 +0200 Subject: [PATCH 06/29] deps: Remove GStreamer mixers from element list --- mopidy/utils/deps.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/mopidy/utils/deps.py b/mopidy/utils/deps.py index b533a18f..ea64a0a0 100644 --- a/mopidy/utils/deps.py +++ b/mopidy/utils/deps.py @@ -143,14 +143,10 @@ def _gstreamer_check_elements(): # Spotify 'appsrc', - # Mixers and sinks - 'alsamixer', + # Audio sinks 'alsasink', - 'ossmixer', 'osssink', - 'oss4mixer', 'oss4sink', - 'pulsemixer', 'pulsesink', # MP3 encoding and decoding From 6ed8132f76c5cfc380933583524b03a9e45846fd Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 25 Jul 2014 10:52:39 +0200 Subject: [PATCH 07/29] http: Allow WebSocket requests from other hosts This makes Tornado 4.0 behavior consistent with previous Tornado versions. Fixes #788 --- docs/changelog.rst | 12 ++++++++++++ mopidy/http/handlers.py | 5 +++++ 2 files changed, 17 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index f28bd8e9..82000ed6 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,6 +4,18 @@ Changelog This changelog is used to track all major changes to Mopidy. + +v0.19.2 (UNRELEASED) +==================== + +Bug fix release. + +**HTTP** + +- When using Tornado 4.0, allow WebSocket requests from other hosts. (Fixes: + :issue:`788`) + + v0.19.1 (2014-07-23) ==================== diff --git a/mopidy/http/handlers.py b/mopidy/http/handlers.py index f35b5c7c..e212c77d 100644 --- a/mopidy/http/handlers.py +++ b/mopidy/http/handlers.py @@ -112,6 +112,11 @@ class WebSocketHandler(tornado.websocket.WebSocketHandler): logger.error('WebSocket request error: %s', e) self.close() + def check_origin(self, origin): + # Allow cross-origin WebSocket connections, like Tornado before 4.0 + # defaulted to. + return True + def set_mopidy_headers(request_handler): request_handler.set_header('Cache-Control', 'no-cache') From e4b54426b497db135176afaa54c19b547b41dcd1 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 25 Jul 2014 12:35:01 +0200 Subject: [PATCH 08/29] audio: Set initial volume on software mixer This must be set after the audio actor has injected itself into the software mixer, else it will have no effect on the GStreamer software mixer. Fixes #791 --- docs/changelog.rst | 5 +++++ mopidy/audio/actor.py | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 82000ed6..9dbadc1a 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -10,6 +10,11 @@ v0.19.2 (UNRELEASED) Bug fix release. +**Audio** + +- Make :confval:`audio/mixer_volume` work on the software mixer again. This + was broken with the mixer changes in 0.19.0. (Fixes: :issue:`791`) + **HTTP** - When using Tornado 4.0, allow WebSocket requests from other hosts. (Fixes: diff --git a/mopidy/audio/actor.py b/mopidy/audio/actor.py index 55aa45b8..dcc51ea4 100644 --- a/mopidy/audio/actor.py +++ b/mopidy/audio/actor.py @@ -195,6 +195,14 @@ class Audio(pykka.ThreadingActor): self._connect(self._playbin, 'notify::volume', self._on_mixer_change) self._connect(self._playbin, 'notify::mute', self._on_mixer_change) + # The Mopidy startup procedure will set the initial volume of a mixer, + # but this happens before the audio actor is injected into the software + # mixer and has no effect. Thus, we need to set the initial volume + # again. + initial_volume = self._config['audio']['mixer_volume'] + if initial_volume is not None: + self._mixer.set_volume(initial_volume) + def _on_mixer_change(self, element, gparamspec): self._mixer.trigger_events_for_changed_values() From fd53e2a31de7511b718f628482d286cb4deb371f Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sat, 26 Jul 2014 11:28:50 +0200 Subject: [PATCH 09/29] docs: Improve readme Use text from www.mopidy.com. Change "Download development snapshot" link, which confused some new users. --- README.rst | 57 ++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 45 insertions(+), 12 deletions(-) diff --git a/README.rst b/README.rst index 784abd24..10ad4d3c 100644 --- a/README.rst +++ b/README.rst @@ -2,24 +2,57 @@ Mopidy ****** -Mopidy is a music server which can play music both from multiple sources, like -your local hard drive, radio streams, and from Spotify and SoundCloud. Searches -combines results from all music sources, and you can mix tracks from all -sources in your play queue. Your playlists from Spotify or SoundCloud are also -available for use. +Mopidy is an extensible music server written in Python. -To control your Mopidy music server, you can use one of Mopidy's web clients, -the Ubuntu Sound Menu, any device on the same network which can control UPnP -MediaRenderers, or any MPD client. MPD clients are available for many -platforms, including Windows, OS X, Linux, Android and iOS. +Mopidy plays music from local disk, Spotify, SoundCloud, Google Play Music, and +more. You edit the playlist from any phone, tablet, or computer using a range +of MPD and web clients. -To get started with Mopidy, check out `the docs `_. +**Stream music from the cloud** + +Vanilla Mopidy only plays music from your local disk and radio streams. +Through extensions, Mopidy can play music from cloud services like Spotify, +SoundCloud, and Google Play Music. With Mopidy's extension support, backends +for new music sources can be easily added. + +**Mopidy is just a server** + +Mopidy is a Python application that runs in a terminal or in the background on +Linux computers or Macs that have network connectivity and audio output. Out of +the box, Mopidy is an MPD and HTTP server. Additional frontends for controlling +Mopidy can be installed from extensions. + +**Everybody use their favorite client** + +You and the people around you can all connect their favorite MPD or web client +to the Mopidy server to search for music and manage the playlist together. With +a browser or MPD client, which is available for all popular operating systems, +you can control the music from any phone, tablet, or computer. + +**Mopidy on Raspberry Pi** + +The Raspberry Pi is a popular device to run Mopidy on, either using Raspbian or +Arch Linux. It is quite slow, but it is very affordable. In fact, the +Kickstarter funded Gramofon: Modern Cloud Jukebox project used Mopidy on a +Raspberry Pi to prototype the Gramofon device. Mopidy is also a major building +block in the Pi Musicbox integrated audio jukebox system for Raspberry Pi. + +**Mopidy is hackable** + +Mopidy's extension support and Python, JSON-RPC, and JavaScript APIs makes +Mopidy perfect for building your own hacks. In one project, a Raspberry Pi was +embedded in an old cassette player. The buttons and volume control are wired up +with GPIO on the Raspberry Pi, and is used to control playback through a custom +Mopidy extension. The cassettes have NFC tags used to select playlists from +Spotify. + +To get started with Mopidy, check out +`the installation docs `_. - `Documentation `_ - `Source code `_ - `Issue tracker `_ -- `CI server `_ -- `Download development snapshot `_ +- `Development branch tarball `_ - IRC: ``#mopidy`` at `irc.freenode.net `_ - Mailing list: `mopidy@googlegroups.com `_ From b606d89df2d4d98bc9dc98a72748bed89044285e Mon Sep 17 00:00:00 2001 From: Ignasi Fosch Date: Sat, 26 Jul 2014 11:56:51 +0200 Subject: [PATCH 10/29] Remember about upgrade changes and sourcing bashrc --- docs/installation/osx.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/installation/osx.rst b/docs/installation/osx.rst index 1513620c..b6184746 100644 --- a/docs/installation/osx.rst +++ b/docs/installation/osx.rst @@ -15,7 +15,7 @@ If you are running OS X, you can install everything needed with Homebrew. #. Install `Homebrew `_. #. If you are already using Homebrew, make sure your installation is up to - date before you continue:: + date before you continue. Notice that this can update other pieces of software, as well:: brew update brew upgrade @@ -32,9 +32,10 @@ If you are running OS X, you can install everything needed with Homebrew. dependencies and will crash. You can either amend your ``PYTHONPATH`` permanently, by adding the - following statement to your shell's init file, e.g. ``~/.bashrc``:: + following statement to your shell's init file, e.g. ``~/.bashrc``, and reload it:: export PYTHONPATH=$(brew --prefix)/lib/python2.7/site-packages:$PYTHONPATH + source ~/.bashrc Or, you can prefix the Mopidy command every time you run it:: From 88749d00abc581f35be7634b99eed780e03b6671 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sat, 26 Jul 2014 12:00:57 +0200 Subject: [PATCH 11/29] docs: Tweak OS X path changes --- docs/installation/osx.rst | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/installation/osx.rst b/docs/installation/osx.rst index b6184746..9c0e059e 100644 --- a/docs/installation/osx.rst +++ b/docs/installation/osx.rst @@ -15,11 +15,14 @@ If you are running OS X, you can install everything needed with Homebrew. #. Install `Homebrew `_. #. If you are already using Homebrew, make sure your installation is up to - date before you continue. Notice that this can update other pieces of software, as well:: + date before you continue:: brew update brew upgrade + Notice that this will upgrade all software on your system that have been + installed with Homebrew. + #. Mopidy works out of box if you have installed Python from Homebrew:: brew install python @@ -32,9 +35,12 @@ If you are running OS X, you can install everything needed with Homebrew. dependencies and will crash. You can either amend your ``PYTHONPATH`` permanently, by adding the - following statement to your shell's init file, e.g. ``~/.bashrc``, and reload it:: + following statement to your shell's init file, e.g. ``~/.bashrc``:: export PYTHONPATH=$(brew --prefix)/lib/python2.7/site-packages:$PYTHONPATH + + And then reload the shell's init file or restart your terminal:: + source ~/.bashrc Or, you can prefix the Mopidy command every time you run it:: From be65225fae5256ccad6e6cde6f377852b2ae8a69 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sat, 26 Jul 2014 12:01:27 +0200 Subject: [PATCH 12/29] docs: Update authors --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index 1bc7c73b..7a20f492 100644 --- a/AUTHORS +++ b/AUTHORS @@ -40,3 +40,4 @@ - Pierpaolo Frasa - Thomas Scholtes - Sam Willcocks +- Ignasi Fosch From 9d493792bf37f0776464239f88144daf2d6d1f19 Mon Sep 17 00:00:00 2001 From: Ignasi Fosch Date: Sat, 26 Jul 2014 12:31:53 +0200 Subject: [PATCH 13/29] version help test fixed --- tests/test_help.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/test_help.py b/tests/test_help.py index 75d545bc..6499cac1 100644 --- a/tests/test_help.py +++ b/tests/test_help.py @@ -14,7 +14,10 @@ class HelpTest(unittest.TestCase): args = [sys.executable, mopidy_dir, '--help'] process = subprocess.Popen( args, - env={'PYTHONPATH': os.path.join(mopidy_dir, '..')}, + env={'PYTHONPATH': ':'.join([ + os.path.join(mopidy_dir, '..'), + os.environ.get('PYTHONPATH', '') + ])}, stdout=subprocess.PIPE) output = process.communicate()[0] self.assertIn('--version', output) From 93bf3ea918d3bf8372df85c216d8a1ab7af3aca6 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 25 Jul 2014 12:24:22 +0200 Subject: [PATCH 14/29] mpd: Fix crash on wrong number of command args Fixes #789 --- mopidy/mpd/protocol/__init__.py | 9 ++++++++- tests/mpd/protocol/test_authentication.py | 6 ++++++ tests/mpd/test_commands.py | 6 +++--- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/mopidy/mpd/protocol/__init__.py b/mopidy/mpd/protocol/__init__.py index 3c501bbb..f0ae814b 100644 --- a/mopidy/mpd/protocol/__init__.py +++ b/mopidy/mpd/protocol/__init__.py @@ -138,7 +138,13 @@ class Commands(object): def validate(*args, **kwargs): if varargs: return func(*args, **kwargs) - callargs = inspect.getcallargs(func, *args, **kwargs) + + try: + callargs = inspect.getcallargs(func, *args, **kwargs) + except TypeError: + raise exceptions.MpdArgError( + 'wrong number of arguments for "%s"' % name) + for key, value in callargs.items(): default = defaults.get(key, object()) if key in validators and value != default: @@ -146,6 +152,7 @@ class Commands(object): callargs[key] = validators[key](value) except ValueError: raise exceptions.MpdArgError('incorrect arguments') + return func(**callargs) validate.auth_required = auth_required diff --git a/tests/mpd/protocol/test_authentication.py b/tests/mpd/protocol/test_authentication.py index 6a39ba81..4937c04f 100644 --- a/tests/mpd/protocol/test_authentication.py +++ b/tests/mpd/protocol/test_authentication.py @@ -19,6 +19,12 @@ class AuthenticationActiveTest(protocol.BaseTestCase): self.assertFalse(self.dispatcher.authenticated) self.assertEqualResponse('ACK [3@0] {password} incorrect password') + def test_authentication_without_password_fails(self): + self.sendRequest('password') + self.assertFalse(self.dispatcher.authenticated) + self.assertEqualResponse( + 'ACK [2@0] {password} wrong number of arguments for "password"') + def test_anything_when_not_authenticated_should_fail(self): self.sendRequest('any request at all') self.assertFalse(self.dispatcher.authenticated) diff --git a/tests/mpd/test_commands.py b/tests/mpd/test_commands.py index bb4effb8..2b4205fe 100644 --- a/tests/mpd/test_commands.py +++ b/tests/mpd/test_commands.py @@ -169,15 +169,15 @@ class TestCommands(unittest.TestCase): def test_call_incorrect_args(self): self.commands.add('foo')(lambda context: context) - with self.assertRaises(TypeError): + with self.assertRaises(exceptions.MpdArgError): self.commands.call(['foo', 'bar']) self.commands.add('bar')(lambda context, required: context) - with self.assertRaises(TypeError): + with self.assertRaises(exceptions.MpdArgError): self.commands.call(['bar', 'bar', 'baz']) self.commands.add('baz')(lambda context, optional=None: context) - with self.assertRaises(TypeError): + with self.assertRaises(exceptions.MpdArgError): self.commands.call(['baz', 'bar', 'baz']) def test_validator_gets_applied_to_required_arg(self): From 5866260928e8a65b499bca065d1f7ac345568db9 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sat, 26 Jul 2014 14:03:12 +0200 Subject: [PATCH 15/29] docs: Update changelog --- docs/changelog.rst | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 9dbadc1a..88aca9ea 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -15,11 +15,16 @@ Bug fix release. - Make :confval:`audio/mixer_volume` work on the software mixer again. This was broken with the mixer changes in 0.19.0. (Fixes: :issue:`791`) -**HTTP** +**HTTP frontend** - When using Tornado 4.0, allow WebSocket requests from other hosts. (Fixes: :issue:`788`) +**MPD frontend** + +- Fix crash when MPD commands are called with the wrong number of arguments. + This was broken with the MPD command changes in 0.19.0. (Fixes: :issue:`789`) + v0.19.1 (2014-07-23) ==================== @@ -32,7 +37,7 @@ Bug fix release. Mopidy continue to work on Debian/Raspbian stable, where Tornado 2.3 is the newest version available. -**HTTP** +**HTTP frontend** - Add missing string interpolation placeholder. From 1fc86574cb568f7bf54ef63b939c4707d7d61d50 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sat, 26 Jul 2014 14:13:08 +0200 Subject: [PATCH 16/29] docs: Update changelog --- docs/changelog.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 88aca9ea..0d32ae5b 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -5,10 +5,11 @@ Changelog This changelog is used to track all major changes to Mopidy. -v0.19.2 (UNRELEASED) +v0.19.2 (2014-07-26) ==================== -Bug fix release. +Bug fix release, directly from the Mopidy development sprint at EuroPython 2014 +in Berlin. **Audio** From 3bc82f82ac8665e04828cc45a8e2449f9bf1792d Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sat, 26 Jul 2014 14:18:50 +0200 Subject: [PATCH 17/29] Bump version to 0.19.2 --- mopidy/__init__.py | 2 +- tests/test_version.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/mopidy/__init__.py b/mopidy/__init__.py index 6ac5f6c0..c6593354 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.19.1' +__version__ = '0.19.2' diff --git a/tests/test_version.py b/tests/test_version.py index c596defd..1f0006a6 100644 --- a/tests/test_version.py +++ b/tests/test_version.py @@ -47,5 +47,6 @@ class VersionTest(unittest.TestCase): self.assertLess(SV('0.18.1'), SV('0.18.2')) self.assertLess(SV('0.18.2'), SV('0.18.3')) self.assertLess(SV('0.18.3'), SV('0.19.0')) - self.assertLess(SV('0.19.0'), SV(__version__)) - self.assertLess(SV(__version__), SV('0.19.2')) + self.assertLess(SV('0.19.0'), SV('0.19.1')) + self.assertLess(SV('0.19.1'), SV(__version__)) + self.assertLess(SV(__version__), SV('0.19.3')) From 76f568e0f0fba1ac9a68dbfb2f094e2f9a7cbcd4 Mon Sep 17 00:00:00 2001 From: Nick Steel Date: Sat, 26 Jul 2014 13:27:52 +0100 Subject: [PATCH 18/29] Added section on running a developed extension Also some typos and updates. --- docs/extensiondev.rst | 60 +++++++++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 25 deletions(-) diff --git a/docs/extensiondev.rst b/docs/extensiondev.rst index dba0fa83..c0d36515 100644 --- a/docs/extensiondev.rst +++ b/docs/extensiondev.rst @@ -5,9 +5,9 @@ Extension development ********************* Mopidy started as simply an MPD server that could play music from Spotify. -Early on Mopidy got multiple "frontends" to expose Mopidy to more than just MPD -clients: for example the scrobbler frontend what scrobbles what you've listened -to to your Last.fm account, the MPRIS frontend that integrates Mopidy into the +Early on, Mopidy got multiple "frontends" to expose Mopidy to more than just MPD +clients: for example the scrobbler frontend that scrobbles your listening +history to your Last.fm account, the MPRIS frontend that integrates Mopidy into the Ubuntu Sound Menu, and the HTTP server and JavaScript player API making web based Mopidy clients possible. In Mopidy 0.9 we added support for multiple music sources without stopping and reconfiguring Mopidy: for example the local @@ -27,7 +27,7 @@ Anatomy of an extension Extensions are located in a Python package called ``mopidy_something`` where "something" is the name of the application, library or web service you want to -integrated with Mopidy. So for example if you plan to add support for a service +integrate with Mopidy. So, for example, if you plan to add support for a service named Soundspot to Mopidy, you would name your extension's Python package ``mopidy_soundspot``. @@ -37,10 +37,6 @@ be something like "Mopidy-Soundspot". Make sure to include the name "Mopidy" somewhere in that name and that you check the capitalization. This is the name users will use when they install your extension from PyPI. -Also make sure the development version link in your package details work so -that people can easily install the development version into their virtualenv -simply by running e.g. ``pip install Mopidy-Soundspot==dev``. - Mopidy extensions must be licensed under an Apache 2.0 (like Mopidy itself), BSD, MIT or more liberal license to be able to be enlisted in the Mopidy documentation. The license text should be included in the ``LICENSE`` file in @@ -79,11 +75,11 @@ the readme of `cookiecutter-mopidy-ext Example README.rst ================== -The README file should quickly tell what the extension does, how to install it, -and how to configure it. The README should contain a development snapshot link -to a tarball of the latest development version of the extension. It's important -that the development snapshot link ends with ``#egg=Mopidy-Something-dev`` for -installation using ``pip install Mopidy-Something==dev`` to work. +The README file should quickly explain what the extension does, how to install +it, and how to configure it. It should also contain a link to a tarball of the +latest development version of the extension. It's important that this link ends +with ``#egg=Mopidy-Something-dev`` for installation using +``pip install Mopidy-Something==dev`` to work. .. code-block:: rst @@ -124,7 +120,7 @@ installation using ``pip install Mopidy-Something==dev`` to work. - `Source code `_ - `Issue tracker `_ - - `Download development snapshot `_ + - `Development branch tarball `_ Changelog @@ -239,9 +235,9 @@ The root of your Python package should have an ``__version__`` attribute with a class named ``Extension`` which inherits from Mopidy's extension base class, :class:`mopidy.ext.Extension`. This is the class referred to in the ``entry_points`` part of ``setup.py``. Any imports of other files in your -extension should be kept inside methods. This ensures that this file can be -imported without raising :exc:`ImportError` exceptions for missing -dependencies, etc. +extension, outside of Mopidy and it's core requirements, should be kept inside +methods. This ensures that this file can be imported without raising +:exc:`ImportError` exceptions for missing dependencies, etc. The default configuration for the extension is defined by the ``get_default_config()`` method in the ``Extension`` class which returns a @@ -252,10 +248,10 @@ an ``enabled`` config which normally should default to ``true``. Provide good defaults for all config values so that as few users as possible will need to change them. The exception is if the config value has security implications; in that case you should default to the most secure configuration. Leave any -configurations that doesn't have meaningful defaults blank, like ``username`` +configurations that don't have meaningful defaults blank, like ``username`` and ``password``. In the example below, we've chosen to maintain the default -config as a separate file named ``ext.conf``. This makes it easy to e.g. -include the default config in documentation without duplicating it. +config as a separate file named ``ext.conf``. This makes it easy to include the +default config in documentation without duplicating it. This is ``mopidy_soundspot/__init__.py``:: @@ -321,6 +317,9 @@ This is ``mopidy_soundspot/__init__.py``:: gobject.type_register(SoundspotMixer) gst.element_register( SoundspotMixer, 'soundspotmixer', gst.RANK_MARGINAL) + + # Or nothing to register e.g. command extension + pass And this is ``mopidy_soundspot/ext.conf``: @@ -393,7 +392,7 @@ such as scanning for media, adding a command is the way to go. Your top level command name will always match your extension name, but you are free to add sub-commands with names of your choosing. -The skeleton of a commands would look like this. See :ref:`commands-api` for +The skeleton of a command would look like this. See :ref:`commands-api` for more details. :: @@ -409,14 +408,14 @@ more details. self.add_argument('--foo') def run(self, args, config, extensions): - # Your backend implementation + # Your command implementation return 0 Example web application ======================= -As of Mopidy 0.19, extensions can use Mopidy's builtin web server to host +As of Mopidy 0.19, extensions can use Mopidy's built-in web server to host static web clients as well as Tornado and WSGI web applications. For several examples, see the :ref:`http-server-api` docs or explore with :ref:`http-explore-extension` extension. @@ -433,6 +432,17 @@ your :meth:`~mopidy.ext.Extension.setup` method register all your custom GStreamer elements. +Running an extension +==================== + +Once your extension is ready to go, to see it in action you'll need to register +it with Mopidy. Typically this is done by running ``python setup.py install`` +from your extension's Git repo root directory. While developing your extension +and to avoid doing this every time you make a change, you can instead run +``python setup.py develop`` to effectively link Mopidy directly with your +development files. + + Python conventions ================== @@ -447,13 +457,13 @@ Use of Mopidy APIs When writing an extension, you should only use APIs documented at :ref:`api-ref`. Other parts of Mopidy, like :mod:`mopidy.utils`, may change at -any time, and is not something extensions should use. +any time and are not something extensions should use. Logging in extensions ===================== -When making servers like Mopidy, logging is essential for understanding what's +For servers like Mopidy, logging is essential for understanding what's going on. We use the :mod:`logging` module from Python's standard library. When creating a logger, always namespace the logger using your Python package name as this will be visible in Mopidy's debug log:: From a2128864e77934dff52ee002fae82a8f39ea7319 Mon Sep 17 00:00:00 2001 From: Nick Steel Date: Sat, 26 Jul 2014 13:38:28 +0100 Subject: [PATCH 19/29] Fixed typo in comment --- mopidy/commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mopidy/commands.py b/mopidy/commands.py index e43f182e..f7c71d4e 100644 --- a/mopidy/commands.py +++ b/mopidy/commands.py @@ -219,7 +219,7 @@ class Command(object): def run(self, *args, **kwargs): """Run the command. - Must be implemented by sub-classes that are not simply and intermediate + Must be implemented by sub-classes that are not simply an intermediate in the command namespace. """ raise NotImplementedError From 711caa9417b1ad0c4a49a111a8e35b3b50e0126f Mon Sep 17 00:00:00 2001 From: dz0ny Date: Sat, 26 Jul 2014 14:54:21 +0200 Subject: [PATCH 20/29] avahi: Service hostname must contain, resolvable fqdn with .local appended to it. --- mopidy/http/actor.py | 4 ++-- mopidy/mpd/actor.py | 2 +- mopidy/zeroconf.py | 10 +++------- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/mopidy/http/actor.py b/mopidy/http/actor.py index a477d939..57e2f46a 100644 --- a/mopidy/http/actor.py +++ b/mopidy/http/actor.py @@ -58,10 +58,10 @@ class HttpFrontend(pykka.ThreadingActor, CoreListener): if self.zeroconf_name: self.zeroconf_http = zeroconf.Zeroconf( stype='_http._tcp', name=self.zeroconf_name, - host=self.hostname, port=self.port) + port=self.port) self.zeroconf_mopidy_http = zeroconf.Zeroconf( stype='_mopidy-http._tcp', name=self.zeroconf_name, - host=self.hostname, port=self.port) + port=self.port) self.zeroconf_http.publish() self.zeroconf_mopidy_http.publish() diff --git a/mopidy/mpd/actor.py b/mopidy/mpd/actor.py index 23d88bf9..49d9556e 100644 --- a/mopidy/mpd/actor.py +++ b/mopidy/mpd/actor.py @@ -43,7 +43,7 @@ class MpdFrontend(pykka.ThreadingActor, CoreListener): if self.zeroconf_name: self.zeroconf_service = zeroconf.Zeroconf( stype='_mpd._tcp', name=self.zeroconf_name, - host=self.hostname, port=self.port) + port=self.port) self.zeroconf_service.publish() def on_stop(self): diff --git a/mopidy/zeroconf.py b/mopidy/zeroconf.py index cdd84792..0f991ba3 100644 --- a/mopidy/zeroconf.py +++ b/mopidy/zeroconf.py @@ -43,21 +43,17 @@ class Zeroconf(object): :type text: list of str """ - def __init__(self, name, port, stype=None, domain=None, - host=None, text=None): + def __init__(self, name, port, stype=None, domain=None, text=None): self.group = None self.stype = stype or '_http._tcp' self.domain = domain or '' self.port = port self.text = text or [] - if host in ('::', '0.0.0.0'): - self.host = '' - else: - self.host = host template = string.Template(name) self.name = template.safe_substitute( - hostname=self.host or socket.getfqdn(), port=self.port) + hostname=socket.getfqdn(), port=self.port) + self.host = '%s.local' % socket.getfqdn() def __str__(self): return 'Zeroconf service %s at [%s]:%d' % ( From f14519a86c5b5b835caeb1cfcff999dee0a23eba Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sat, 26 Jul 2014 15:12:31 +0200 Subject: [PATCH 21/29] docs: Update changelog wrt PR #795 --- docs/changelog.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 0d32ae5b..865bde2f 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -5,6 +5,13 @@ Changelog This changelog is used to track all major changes to Mopidy. +v0.19.3 (UNRELEASED) +==================== + +- Fix Zeroconf discovery by adding ``.local`` to the announced hostname. (PR: + :issue:`795`) + + v0.19.2 (2014-07-26) ==================== From 22bf058c34b25e1cb802c707e38e46c3ddfa005f Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sat, 26 Jul 2014 15:35:29 +0200 Subject: [PATCH 22/29] Update .mailmap --- .mailmap | 1 + 1 file changed, 1 insertion(+) diff --git a/.mailmap b/.mailmap index a3a35c2a..d380162e 100644 --- a/.mailmap +++ b/.mailmap @@ -15,3 +15,4 @@ Janez Troha Janez Troha Luke Giuliani Colin Montgomerie +Ignasi Fosch From 8459464f34d0eebfe3eba12bd7e68e06c938bc9b Mon Sep 17 00:00:00 2001 From: Ignasi Fosch Date: Sat, 26 Jul 2014 16:31:37 +0200 Subject: [PATCH 23/29] Fix mopidy/audio/scan.py to avoid negative values in track lengths The fix is the most elegant I found, but it might pass undetected. --- mopidy/audio/scan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mopidy/audio/scan.py b/mopidy/audio/scan.py index e872d88c..755ab366 100644 --- a/mopidy/audio/scan.py +++ b/mopidy/audio/scan.py @@ -186,7 +186,7 @@ def audio_data_to_track(data): track_kwargs['date'] = _date(tags) track_kwargs['last_modified'] = int(data.get('mtime') or 0) - track_kwargs['length'] = (data.get(gst.TAG_DURATION) or 0) // gst.MSECOND + track_kwargs['length'] = max(0, (data.get(gst.TAG_DURATION) or 0)) // gst.MSECOND # Clear out any empty values we found track_kwargs = {k: v for k, v in track_kwargs.items() if v} From b13505ea4560c6754186ab9d3c60a0a59dbd64ca Mon Sep 17 00:00:00 2001 From: Ignasi Fosch Date: Sat, 26 Jul 2014 16:42:42 +0200 Subject: [PATCH 24/29] Fix the Line too long lint error --- mopidy/audio/scan.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mopidy/audio/scan.py b/mopidy/audio/scan.py index 755ab366..6c23e954 100644 --- a/mopidy/audio/scan.py +++ b/mopidy/audio/scan.py @@ -186,7 +186,8 @@ def audio_data_to_track(data): track_kwargs['date'] = _date(tags) track_kwargs['last_modified'] = int(data.get('mtime') or 0) - track_kwargs['length'] = max(0, (data.get(gst.TAG_DURATION) or 0)) // gst.MSECOND + track_kwargs['length'] = max( + 0, (data.get(gst.TAG_DURATION) or 0)) // gst.MSECOND # Clear out any empty values we found track_kwargs = {k: v for k, v in track_kwargs.items() if v} From 5daa95cae8b78b47105740578acc657bc2329f0a Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sat, 26 Jul 2014 16:54:32 +0200 Subject: [PATCH 25/29] docs: Update changelog --- docs/changelog.rst | 44 +++++++++++++++++++------------------------- 1 file changed, 19 insertions(+), 25 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 865bde2f..ac8084d3 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -8,7 +8,12 @@ This changelog is used to track all major changes to Mopidy. v0.19.3 (UNRELEASED) ==================== -- Fix Zeroconf discovery by adding ``.local`` to the announced hostname. (PR: +Bug fix release. + +- Audio: Fix negative track length for radio streams. (Fixes: :issue:`662`, + PR: :issue:`796`) + +- Zeroconf: Fix discovery by adding ``.local`` to the announced hostname. (PR: :issue:`795`) @@ -18,20 +23,15 @@ v0.19.2 (2014-07-26) Bug fix release, directly from the Mopidy development sprint at EuroPython 2014 in Berlin. -**Audio** - -- Make :confval:`audio/mixer_volume` work on the software mixer again. This +- Audio: Make :confval:`audio/mixer_volume` work on the software mixer again. This was broken with the mixer changes in 0.19.0. (Fixes: :issue:`791`) -**HTTP frontend** +- HTTP frontend: When using Tornado 4.0, allow WebSocket requests from other + hosts. (Fixes: :issue:`788`) -- When using Tornado 4.0, allow WebSocket requests from other hosts. (Fixes: - :issue:`788`) - -**MPD frontend** - -- Fix crash when MPD commands are called with the wrong number of arguments. - This was broken with the MPD command changes in 0.19.0. (Fixes: :issue:`789`) +- MPD frontend: Fix crash when MPD commands are called with the wrong number of + arguments. This was broken with the MPD command changes in 0.19.0. (Fixes: + :issue:`789`) v0.19.1 (2014-07-23) @@ -39,21 +39,15 @@ v0.19.1 (2014-07-23) Bug fix release. -**Dependencies** +- Dependencies: Mopidy now requires Tornado >= 2.3, instead of >= 3.1. This + should make Mopidy continue to work on Debian/Raspbian stable, where Tornado + 2.3 is the newest version available. -- Mopidy now requires Tornado >= 2.3, instead of >= 3.1. This should make - Mopidy continue to work on Debian/Raspbian stable, where Tornado 2.3 is the - newest version available. +- HTTP frontend: Add missing string interpolation placeholder. -**HTTP frontend** - -- Add missing string interpolation placeholder. - -**Development** - -- ``mopidy --version`` and :meth:`mopidy.core.Core.get_version` now returns the - correct version when Mopidy is run from a Git repo other than Mopidy's own. - (Related to :issue:`706`) +- Development: ``mopidy --version`` and :meth:`mopidy.core.Core.get_version` + now returns the correct version when Mopidy is run from a Git repo other than + Mopidy's own. (Related to :issue:`706`) v0.19.0 (2014-07-21) From e9289ca554fe0e9f755e8156175f76dc001bd536 Mon Sep 17 00:00:00 2001 From: Ignasi Fosch Date: Sun, 27 Jul 2014 16:34:28 +0200 Subject: [PATCH 26/29] Checks for musicbrainz album ID and sets images to a list containing the corresponding URL --- mopidy/audio/scan.py | 12 ++++++++++++ mopidy/local/commands.py | 3 ++- tests/audio/test_scan.py | 14 +++++++++++++- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/mopidy/audio/scan.py b/mopidy/audio/scan.py index 6c23e954..2f6ac4c3 100644 --- a/mopidy/audio/scan.py +++ b/mopidy/audio/scan.py @@ -150,6 +150,18 @@ def _date(tags): return None +def add_musicbrainz_cover_art(track): + if track.album is not None and track.album.musicbrainz_id is not None: + base = "http://coverartarchive.org/release" + images = frozenset( + ["{}/{}/front".format( + base, + track.album.musicbrainz_id)]) + album = track.album.copy(images=images) + track = track.copy(album=album) + return track + + def audio_data_to_track(data): """Convert taglist data + our extras to a track.""" tags = data['tags'] diff --git a/mopidy/local/commands.py b/mopidy/local/commands.py index 1e7839a5..f2a7ec24 100644 --- a/mopidy/local/commands.py +++ b/mopidy/local/commands.py @@ -118,7 +118,8 @@ class ScanCommand(commands.Command): relpath = translator.local_track_uri_to_path(uri, media_dir) file_uri = path.path_to_uri(os.path.join(media_dir, relpath)) data = scanner.scan(file_uri) - track = scan.audio_data_to_track(data).copy(uri=uri) + track = scan.add_musicbrainz_cover_art( + scan.audio_data_to_track(data).copy(uri=uri)).copy(uri=uri) library.add(track) logger.debug('Added %s', track.uri) except exceptions.ScannerError as error: diff --git a/tests/audio/test_scan.py b/tests/audio/test_scan.py index 26caa422..1e352991 100644 --- a/tests/audio/test_scan.py +++ b/tests/audio/test_scan.py @@ -71,6 +71,11 @@ class TranslatorTest(unittest.TestCase): actual = scan.audio_data_to_track(self.data) self.assertEqual(expected, actual) + def check_local(self, expected): + actual = scan.add_musicbrainz_cover_art( + scan.audio_data_to_track(self.data)) + self.assertEqual(expected, actual) + def test_track(self): self.check(self.track) @@ -197,13 +202,20 @@ class TranslatorTest(unittest.TestCase): def test_missing_album_musicbrainz_id(self): del self.data['tags']['musicbrainz-albumid'] - album = self.track.album.copy(musicbrainz_id=None) + album = self.track.album.copy(musicbrainz_id=None, + images=[]) self.check(self.track.copy(album=album)) def test_multiple_album_musicbrainz_id(self): self.data['tags']['musicbrainz-albumid'].append('id') self.check(self.track) + def test_album_musicbrainz_id_cover(self): + album = self.track.album.copy( + images=frozenset( + ['http://coverartarchive.org/release/albumid/front'])) + self.check_local(self.track.copy(album=album)) + def test_missing_album_num_tracks(self): del self.data['tags']['track-count'] album = self.track.album.copy(num_tracks=None) From 13073362f4d6ae05c59104816d1ec281824577bb Mon Sep 17 00:00:00 2001 From: Ignasi Fosch Date: Sun, 27 Jul 2014 17:52:14 +0200 Subject: [PATCH 27/29] Correct if .. is not None --- mopidy/audio/scan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mopidy/audio/scan.py b/mopidy/audio/scan.py index 2f6ac4c3..53f00ac0 100644 --- a/mopidy/audio/scan.py +++ b/mopidy/audio/scan.py @@ -151,7 +151,7 @@ def _date(tags): def add_musicbrainz_cover_art(track): - if track.album is not None and track.album.musicbrainz_id is not None: + if track.album and track.album.musicbrainz_id: base = "http://coverartarchive.org/release" images = frozenset( ["{}/{}/front".format( From 6367b837350bf9126130fc1ed3ad9b6600d6d916 Mon Sep 17 00:00:00 2001 From: dz0ny Date: Mon, 28 Jul 2014 20:48:06 +0200 Subject: [PATCH 28/29] Test with multiple versions of tornado. See #798 --- .travis.yml | 2 ++ tox.ini | 14 ++++++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index c838e7d0..77a8d8a3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,8 @@ python: env: - TOX_ENV=py27 + - TOX_ENV=tornado2.3 + - TOX_ENV=tornado3.2 - TOX_ENV=docs - TOX_ENV=flake8 diff --git a/tox.ini b/tox.ini index e8946523..93447015 100644 --- a/tox.ini +++ b/tox.ini @@ -1,13 +1,23 @@ [tox] -envlist = py27, docs, flake8 +envlist = tornado2.3, tornado3.2, py27, docs, flake8 [testenv] sitepackages = true +commands = nosetests -v --with-xunit --xunit-file=xunit-{envname}.xml --with-coverage --cover-package=mopidy deps = coverage mock nose -commands = nosetests -v --with-xunit --xunit-file=xunit-{envname}.xml --with-coverage --cover-package=mopidy + +[testenv:tornado2.3] +commands = nosetests -v tests/http +deps = {[testenv]deps} + tornado==2.3 + +[testenv:tornado3.2] +commands = nosetests -v tests/http +deps = {[testenv]deps} + tornado==3.1 [testenv:docs] deps = -r{toxinidir}/docs/requirements.txt From acef38a6c75bb46324d4a49b57552f2320f02694 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Mon, 28 Jul 2014 23:41:17 +0200 Subject: [PATCH 29/29] tox: Fix Tornado version mismatch in env name and deps --- tox.ini | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/tox.ini b/tox.ini index 93447015..ed6f0271 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = tornado2.3, tornado3.2, py27, docs, flake8 +envlist = py27, py27-tornado23, py27-tornado31, docs, flake8 [testenv] sitepackages = true @@ -9,14 +9,16 @@ deps = mock nose -[testenv:tornado2.3] +[testenv:py27-tornado23] commands = nosetests -v tests/http -deps = {[testenv]deps} +deps = + {[testenv]deps} tornado==2.3 -[testenv:tornado3.2] +[testenv:py27-tornado31] commands = nosetests -v tests/http -deps = {[testenv]deps} +deps = + {[testenv]deps} tornado==3.1 [testenv:docs]