From 08a8d5c43be271a41af74ce0c7df9d871cf2b532 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sun, 14 Dec 2014 14:16:37 +0100 Subject: [PATCH 01/21] mpd: Remove "Comment" tag type from translator output. Newer versions of the protocol have removed this tag, so we should as well. This also works around the issue of #881 which was breaking things with newlines in comment fields. The readcomments command seems to replace this, but it seems to only care about specific extra tagtypes, not the general comment tag we normally collect when scanning things. --- mopidy/mpd/translator.py | 3 --- tests/mpd/test_translator.py | 4 ++-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/mopidy/mpd/translator.py b/mopidy/mpd/translator.py index ec3a270b..23fb2874 100644 --- a/mopidy/mpd/translator.py +++ b/mopidy/mpd/translator.py @@ -81,9 +81,6 @@ def track_to_mpd_format(track, position=None): if track.disc_no: result.append(('Disc', track.disc_no)) - if track.comment: - result.append(('Comment', track.comment)) - if track.musicbrainz_id is not None: result.append(('MUSICBRAINZ_TRACKID', track.musicbrainz_id)) return result diff --git a/tests/mpd/test_translator.py b/tests/mpd/test_translator.py index b38a93e8..82e60d93 100644 --- a/tests/mpd/test_translator.py +++ b/tests/mpd/test_translator.py @@ -73,10 +73,10 @@ class TrackMpdFormatTest(unittest.TestCase): self.assertIn(('Track', '7/13'), result) self.assertIn(('Date', datetime.date(1977, 1, 1)), result) self.assertIn(('Disc', '1'), result) - self.assertIn(('Comment', 'a comment'), result) self.assertIn(('Pos', 9), result) self.assertIn(('Id', 122), result) - self.assertEqual(len(result), 15) + self.assertNotIn(('Comment', 'a comment'), result) + self.assertEqual(len(result), 14) def test_track_to_mpd_format_musicbrainz_trackid(self): track = self.track.copy(musicbrainz_id='foo') From 541412dbfc33d80727db5dbba067fdcc9d922470 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sun, 14 Dec 2014 14:35:13 +0100 Subject: [PATCH 02/21] mpd: Remove newline escaping code. This was added for #881, where the correct fix turned out to be to remove comments from the responses. We should still add some sanity checks for verifying that our responses at the very least only contain printable chars. --- mopidy/mpd/dispatcher.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/mopidy/mpd/dispatcher.py b/mopidy/mpd/dispatcher.py index 8167a774..5d9cecd9 100644 --- a/mopidy/mpd/dispatcher.py +++ b/mopidy/mpd/dispatcher.py @@ -196,17 +196,12 @@ class MpdDispatcher(object): def _format_lines(self, line): if isinstance(line, dict): - return [self._escape_newlines('%s: %s' % (key, value)) - for (key, value) - in line.items()] + return ['%s: %s' % (key, value) for (key, value) in line.items()] if isinstance(line, tuple): (key, value) = line - return [self._escape_newlines('%s: %s' % (key, value))] + return ['%s: %s' % (key, value)] return [line] - def _escape_newlines(self, text): - return text.replace('\n', '\\n') - class MpdContext(object): """ From f1fd21f647b9ee734453b2b47d8bc3675dc93d98 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 16 Dec 2014 23:00:19 +0100 Subject: [PATCH 03/21] docs: Add upmpdcli as an alternative to Rygel Based on PR #871 by @woutervanwijk. Fixes #871 --- docs/clients/upnp.rst | 52 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 45 insertions(+), 7 deletions(-) diff --git a/docs/clients/upnp.rst b/docs/clients/upnp.rst index 7f21a6c6..9b24ae46 100644 --- a/docs/clients/upnp.rst +++ b/docs/clients/upnp.rst @@ -18,7 +18,7 @@ DLNA Digital Media Server (DMS) / UPnP AV MediaServer: A MediaServer provides a library of media and is capable of streaming that media to a MediaRenderer. If Mopidy was a MediaServer, you could browse and play Mopidy's music on a TV, smartphone, or tablet supporting UPnP. Mopidy - does not currently support this, but we may in the future. :issue:`52` is + does not currently support this, but we may in the future. :issue:`52` is the relevant wishlist issue. DLNA Digital Media Renderer (DMR) / UPnP AV MediaRenderer: @@ -27,13 +27,51 @@ DLNA Digital Media Renderer (DMR) / UPnP AV MediaRenderer: given media, typically served by a MediaServer. If Mopidy was a MediaRenderer, you could use e.g. your smartphone or tablet to make Mopidy play media. Mopidy *does already* have experimental support for being a - MediaRenderer with the help of Rygel, as you can read more about below. + MediaRenderer, as you can read more about below. + + +Mopidy as an UPnP MediaRenderer +=============================== + +There are two ways Mopidy can be made available as an UPnP MediaRenderer: +Using Mopidy-MPRIS and Rygel, or using Mopidy-MPD and upmpdcli. + + +.. _upmpdcli: + +upmpdcli +-------- + +`upmpdcli `_ is recommended, since it +is easier to setup, and offers `OpenHome ohMedia`_ +compatibility. upmpdcli exposes a UPnP MediaRenderer to the network, while +using the MPD protocol to control Mopidy. + +1. Install upmpdcli. On Debian/Ubuntu:: + + apt-get install upmpdcli + + Alternatively, follow the instructions from the upmpdcli website. + +2. The default settings of upmpdcli will work with the default settings of + :ref:`ext-mpd`. Edit :file:`/etc/upmpdcli.conf` if you want to use different + ports, hosts, or other settings. + +3. Start upmpdcli using the command:: + + upmpdcli + + Or, run it in the background as a service:: + + sudo service upmpdcli start + +4. A UPnP renderer should be available now. .. _rygel: -How to make Mopidy available as an UPnP MediaRenderer -===================================================== +Rygel +----- With the help of `the Rygel project `_ Mopidy can be made available as an UPnP MediaRenderer. Rygel will interface with the MPRIS @@ -41,15 +79,15 @@ interface provided by the `Mopidy-MPRIS extension `_, and make Mopidy available as a MediaRenderer on the local network. Since this depends on the MPRIS frontend, which again depends on D-Bus being available, this will only work on Linux, and -not OS X. MPRIS/D-Bus is only available to other applications on the same +not OS X. MPRIS/D-Bus is only available to other applications on the same host, so Rygel must be running on the same machine as Mopidy. -1. Start Mopidy and make sure the MPRIS frontend is working. It is activated +1. Start Mopidy and make sure the MPRIS frontend is working. It is activated by default when the Mopidy-MPRIS extension is installed, but you may miss dependencies or be using OS X, in which case it will not work. Check the console output when Mopidy is started for any errors related to the MPRIS frontend. If you're unsure it is working, there are instructions for how to - test it on in the `Mopidy-MPRIS readme + test it in the `Mopidy-MPRIS readme `_. 2. Install Rygel. On Debian/Ubuntu:: From 02489b02bec23b33895bca9889aeed5a30d1d2cf Mon Sep 17 00:00:00 2001 From: Deni Bertovic Date: Wed, 3 Dec 2014 21:30:32 +0100 Subject: [PATCH 04/21] Add appendinx about xbmc issues XBMC won't boot because a different libtag version got installed with mopidy system wide, and XBMC picks that up insted of the one it vendors in. Added info how to fix this. --- docs/installation/raspberrypi.rst | 33 +++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/docs/installation/raspberrypi.rst b/docs/installation/raspberrypi.rst index c13e3ec4..a11aec9c 100644 --- a/docs/installation/raspberrypi.rst +++ b/docs/installation/raspberrypi.rst @@ -74,12 +74,12 @@ you a lot better performance. #. Install Mopidy and its dependencies as described in :ref:`debian-install`. #. Finally, you need to set a couple of :doc:`config values `, and - then you're ready to :doc:`run Mopidy `. Alternatively you may + then you're ready to :doc:`run Mopidy `. Alternatively you may want to have Mopidy run as a :doc:`system service `, automatically starting at boot. -Appendix: Fixing audio quality issues +Appendix A: Fixing audio quality issues ===================================== As of about April 2013 the following steps should resolve any audio @@ -141,3 +141,32 @@ not determined the exact revision that fixed this:: The only remaining known issue is a slight gap in playback at track changes this is likely due to gapless playback not being implemented and is being worked on irrespective of Raspberry Pi related work. + + +Appendix B: XBMC not booting? +============================= + +A very important note for Raspbmc users. Due to a dependency version problem +you might have to make some minor configurations for XBMC to start +properly (this depends on your system’s version). + +If you notice that XBMC is not starting but gets stuck in a loop, +you need to make the following changes. +In short, what you have to do is the following:: + + sudo ln -sf /home/pi/.xbmc-current/xbmc-bin/lib/xbmc/system/libtag.so.1 \ + /usr/lib/arm-linux-gnueabihf/libtag.so.1 + +However, this will not persist the changes. +To persist the changes edit :file:`/etc/ld.so.conf.d/arm-linux-gnueabihf.conf` +and add the following at the top:: + + /home/pi/.xbmc-current/xbmc-bin/lib/xbmc/system + +It's very important to add it at the top of the file as this indicates the priority of the folder +in which to look for shared libraries. XBMC doesn't play nicely with the system wide installed +version of libtag (that got installed with Mopidy) but rather vendors in it's own version. +Hopefully this will fix the issue for you if you're running Raspbmc. +More info about this issue can be found in this `post ` _ as well. +Please note that if you're running Xbian or any other distribution that the instructions might vary. + From 217919884e7154dc6e8cd04cb7f1846bea42901d Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 16 Dec 2014 23:20:18 +0100 Subject: [PATCH 05/21] docs: Edit Raspbmc appendix a bit --- docs/installation/raspberrypi.rst | 37 +++++++++++++++++-------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/docs/installation/raspberrypi.rst b/docs/installation/raspberrypi.rst index a11aec9c..3c70340c 100644 --- a/docs/installation/raspberrypi.rst +++ b/docs/installation/raspberrypi.rst @@ -80,7 +80,7 @@ you a lot better performance. Appendix A: Fixing audio quality issues -===================================== +======================================= As of about April 2013 the following steps should resolve any audio issues for HDMI and analog without the use of an external USB sound @@ -143,30 +143,33 @@ this is likely due to gapless playback not being implemented and is being worked on irrespective of Raspberry Pi related work. -Appendix B: XBMC not booting? -============================= +Appendix B: Raspbmc not booting +=============================== -A very important note for Raspbmc users. Due to a dependency version problem -you might have to make some minor configurations for XBMC to start -properly (this depends on your system’s version). +Due to a dependency version problem where XBMC uses another version of libtag +than what Debian originally ships with, you might have to make some minor +changes for Raspbmc to start properly after installing Mopidy. If you notice that XBMC is not starting but gets stuck in a loop, -you need to make the following changes. -In short, what you have to do is the following:: +you need to make the following changes:: sudo ln -sf /home/pi/.xbmc-current/xbmc-bin/lib/xbmc/system/libtag.so.1 \ /usr/lib/arm-linux-gnueabihf/libtag.so.1 -However, this will not persist the changes. -To persist the changes edit :file:`/etc/ld.so.conf.d/arm-linux-gnueabihf.conf` -and add the following at the top:: +However, this will not persist the changes. To persist the changes edit +:file:`/etc/ld.so.conf.d/arm-linux-gnueabihf.conf` and add the following at the +top:: /home/pi/.xbmc-current/xbmc-bin/lib/xbmc/system -It's very important to add it at the top of the file as this indicates the priority of the folder -in which to look for shared libraries. XBMC doesn't play nicely with the system wide installed -version of libtag (that got installed with Mopidy) but rather vendors in it's own version. -Hopefully this will fix the issue for you if you're running Raspbmc. -More info about this issue can be found in this `post ` _ as well. -Please note that if you're running Xbian or any other distribution that the instructions might vary. +It's very important to add it at the top of the file as this indicates the +priority of the folder in which to look for shared libraries. +XBMC doesn't play nicely with the system wide installed version of libtag that +got installed together with Mopidy, but rather vendors in its own version. + +More info about this issue can be found in `this post +`_. + +Please note that if you're running Xbian or another XBMC distribution these +instructions might vary for your system. From 6e55435aa90fbeb2d4d8e3906c8e56414b03aa9e Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sat, 13 Dec 2014 01:26:41 +0100 Subject: [PATCH 06/21] mpd: Enable browsing of empty dirs This was disabled together with a bunch of other changes without any explanation in commit f24ca36e5a5b72c886d6d8e8df88be79fb094dda. I'm guessing that this wasn't intentional, and no test covered the case. (cherry picked from commit 4e508cd01750bcef9e0e9c7b70798eacd276aef8) --- mopidy/mpd/protocol/music_db.py | 2 -- tests/mpd/protocol/test_music_db.py | 7 +++++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/mopidy/mpd/protocol/music_db.py b/mopidy/mpd/protocol/music_db.py index a5757915..5a7f51fb 100644 --- a/mopidy/mpd/protocol/music_db.py +++ b/mopidy/mpd/protocol/music_db.py @@ -421,8 +421,6 @@ def lsinfo(context, uri=None): if uri in (None, '', '/'): result.extend(protocol.stored_playlists.listplaylists(context)) - if not result: - raise exceptions.MpdNoExistError('Not found') return result diff --git a/tests/mpd/protocol/test_music_db.py b/tests/mpd/protocol/test_music_db.py index e6712fef..8efd1dc4 100644 --- a/tests/mpd/protocol/test_music_db.py +++ b/tests/mpd/protocol/test_music_db.py @@ -347,6 +347,13 @@ class MusicDatabaseHandlerTest(protocol.BaseTestCase): self.assertInResponse('directory: dummy/foo') self.assertInResponse('OK') + def test_lsinfo_for_empty_dir_returns_nothing(self): + self.backend.library.dummy_browse_result = { + 'dummy:/': []} + + self.sendRequest('lsinfo "/dummy"') + self.assertInResponse('OK') + def test_lsinfo_for_dir_does_not_recurse(self): self.backend.library.dummy_library = [ Track(uri='dummy:/a', name='a'), From b365d2494bceb84c328c2f6888bb139ba3b43376 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sun, 14 Dec 2014 14:16:37 +0100 Subject: [PATCH 07/21] mpd: Remove "Comment" tag type from translator output. Newer versions of the protocol have removed this tag, so we should as well. This also works around the issue of #881 which was breaking things with newlines in comment fields. The readcomments command seems to replace this, but it seems to only care about specific extra tagtypes, not the general comment tag we normally collect when scanning things. (cherry picked from commit 08a8d5c43be271a41af74ce0c7df9d871cf2b532) --- mopidy/mpd/translator.py | 3 --- tests/mpd/test_translator.py | 4 ++-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/mopidy/mpd/translator.py b/mopidy/mpd/translator.py index f3264a46..8a43f929 100644 --- a/mopidy/mpd/translator.py +++ b/mopidy/mpd/translator.py @@ -81,9 +81,6 @@ def track_to_mpd_format(track, position=None): if track.disc_no: result.append(('Disc', track.disc_no)) - if track.comment: - result.append(('Comment', track.comment)) - if track.musicbrainz_id is not None: result.append(('MUSICBRAINZ_TRACKID', track.musicbrainz_id)) return result diff --git a/tests/mpd/test_translator.py b/tests/mpd/test_translator.py index 2bd6cff6..05a0d187 100644 --- a/tests/mpd/test_translator.py +++ b/tests/mpd/test_translator.py @@ -73,10 +73,10 @@ class TrackMpdFormatTest(unittest.TestCase): self.assertIn(('Track', '7/13'), result) self.assertIn(('Date', datetime.date(1977, 1, 1)), result) self.assertIn(('Disc', '1'), result) - self.assertIn(('Comment', 'a comment'), result) self.assertIn(('Pos', 9), result) self.assertIn(('Id', 122), result) - self.assertEqual(len(result), 15) + self.assertNotIn(('Comment', 'a comment'), result) + self.assertEqual(len(result), 14) def test_track_to_mpd_format_musicbrainz_trackid(self): track = self.track.copy(musicbrainz_id='foo') From d2bf3f6d830fb5d4f929af324052f1699c82fcb7 Mon Sep 17 00:00:00 2001 From: Thomas Amland Date: Thu, 11 Dec 2014 14:41:37 +0100 Subject: [PATCH 08/21] [local] fix modified files not being updated (cherry picked from commit dfd897832a952c4a7da655f504dc0c162433c539) --- mopidy/local/commands.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mopidy/local/commands.py b/mopidy/local/commands.py index 1e7839a5..a0dc23d1 100644 --- a/mopidy/local/commands.py +++ b/mopidy/local/commands.py @@ -70,7 +70,6 @@ class ScanCommand(commands.Command): library = _get_library(args, config) - uris_in_library = set() uris_to_update = set() uris_to_remove = set() @@ -87,7 +86,7 @@ class ScanCommand(commands.Command): logger.debug('Missing file %s', track.uri) uris_to_remove.add(track.uri) elif mtime > track.last_modified: - uris_in_library.add(track.uri) + uris_to_update.add(track.uri) logger.info('Removing %d missing tracks.', len(uris_to_remove)) for uri in uris_to_remove: From 188c9ef26fd193b7e913abb9997ebe87fd1dbeca Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 16 Dec 2014 23:34:00 +0100 Subject: [PATCH 09/21] docs: Update changelog --- docs/changelog.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index bddad372..979a5a0c 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -22,6 +22,13 @@ Bug fix release. :attr:`mopidy.models.Track.track_no`, and :attr:`mopidy.models.Track.last_modified` from ``0`` to :class:`None`. +- Local: Fix scanning of modified files. (PR: :issue:`904`) + +- MPD: Re-enable browsing of empty directories. (PR: :issue:`906`) + +- MPD: Remove track comments from responses. They are not included by the + original MPD server, and this works around :issue:`881`. (PR: :issue:`882`) + v0.19.4 (2014-09-01) ==================== From b300c090341450a5337c434ab6c521733f973b4e Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 16 Dec 2014 23:35:42 +0100 Subject: [PATCH 10/21] docs: Update authors --- .mailmap | 1 + AUTHORS | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/.mailmap b/.mailmap index d380162e..45935be6 100644 --- a/.mailmap +++ b/.mailmap @@ -16,3 +16,4 @@ Janez Troha Luke Giuliani Colin Montgomerie Ignasi Fosch +Christopher Schirner diff --git a/AUTHORS b/AUTHORS index e36d953d..b7bad761 100644 --- a/AUTHORS +++ b/AUTHORS @@ -42,3 +42,7 @@ - Sam Willcocks - Ignasi Fosch - Arjun Naik +- Christopher Schirner +- Dmitry Sandalov +- Deni Bertovic +- Thomas Amland From 70829390d18173ab2a74b4d5be71014561f540f5 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 16 Dec 2014 23:39:22 +0100 Subject: [PATCH 11/21] docs: Fix typo --- docs/changelog.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 2995f1a4..a0371840 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -50,7 +50,8 @@ v0.20.0 (UNRELEASED) **Audio** - Deprecated :meth:`mopidy.audio.Audio.emit_end_of_stream`. Pass a - :class:`None` buffer to :meth:`mopidy.audio.Audio.emit_data` end the stream. + :class:`None` buffer to :meth:`mopidy.audio.Audio.emit_data` to end the + stream. - Internal code cleanup within audio subsystem: From b6cf86c6a2060d5a79dce913ccd7e439b00822e6 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Fri, 19 Dec 2014 22:37:48 +0100 Subject: [PATCH 12/21] startup: Log backend and frontend startup times. Allows us to debug cases where a "bad" extension is blocking the startup. In there future we might also warning log extension that take longer than some threshold to help find these cases. --- mopidy/commands.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/mopidy/commands.py b/mopidy/commands.py index 4b00a685..fecabe98 100644 --- a/mopidy/commands.py +++ b/mopidy/commands.py @@ -2,9 +2,11 @@ from __future__ import absolute_import, print_function, unicode_literals import argparse import collections +import contextlib import logging import os import sys +import time import glib @@ -63,6 +65,13 @@ class _HelpAction(argparse.Action): raise _HelpError() +@contextlib.contextmanager +def _startup_timer(name): + start = time.time() + yield + logger.debug('%s startup took %dms', name, (time.time() - start) * 1000) + + class Command(object): """Command parser and runner for building trees of commands. @@ -339,8 +348,9 @@ class RootCommand(Command): backends = [] for backend_class in backend_classes: try: - backend = backend_class.start( - config=config, audio=audio).proxy() + with _startup_timer(backend_class.__name__): + backend = backend_class.start( + config=config, audio=audio).proxy() backends.append(backend) except exceptions.BackendError as exc: logger.error( @@ -361,7 +371,8 @@ class RootCommand(Command): for frontend_class in frontend_classes: try: - frontend_class.start(config=config, core=core) + with _startup_timer(frontend_class.__name__): + frontend_class.start(config=config, core=core) except exceptions.FrontendError as exc: logger.error( 'Frontend (%s) initialization error: %s', From 9a2f8a3e4f5ea80ba0e6c497acfc8bee40a04375 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sat, 20 Dec 2014 21:32:08 +0100 Subject: [PATCH 13/21] http: Log errors instead of dying for HTTP startup. --- docs/changelog.rst | 5 +++++ mopidy/http/actor.py | 7 ++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index a0371840..36b81f25 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -69,6 +69,11 @@ v0.20.0 (UNRELEASED) make sense for a server such as Mopidy. Currently the only way to find out if it is in use and will be missed is to go ahead and remove it. +**HTTP** + +- Log error while starting HTTP apps instead letting the HTTP server thread + die. (Fixes: :issue:`875`) + v0.19.5 (UNRELEASED) ==================== diff --git a/mopidy/http/actor.py b/mopidy/http/actor.py index d37a5672..200ef833 100644 --- a/mopidy/http/actor.py +++ b/mopidy/http/actor.py @@ -129,11 +129,16 @@ class HttpServer(threading.Thread): def _get_app_request_handlers(self): result = [] for app in self.apps: + try: + request_handlers = app['factory'](self.config, self.core) + except Exception: + logger.exception('Loading %s failed.', app['name']) + continue + result.append(( r'/%s' % app['name'], handlers.AddSlashHandler )) - request_handlers = app['factory'](self.config, self.core) for handler in request_handlers: handler = list(handler) handler[0] = '/%s%s' % (app['name'], handler[0]) From 94bdb88b9cb161dd9736c74c1804c03246d235a3 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sat, 20 Dec 2014 21:32:08 +0100 Subject: [PATCH 14/21] http: Log errors instead of dying for HTTP startup. (cherry picked from commit 9a2f8a3e4f5ea80ba0e6c497acfc8bee40a04375) Conflicts: docs/changelog.rst --- docs/changelog.rst | 3 +++ mopidy/http/actor.py | 7 ++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 979a5a0c..ba00ed1f 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -29,6 +29,9 @@ Bug fix release. - MPD: Remove track comments from responses. They are not included by the original MPD server, and this works around :issue:`881`. (PR: :issue:`882`) +- HTTP: Errors while starting HTTP apps are logged instead of crashing the HTTP + server. (Fixes: :issue:`875`) + v0.19.4 (2014-09-01) ==================== diff --git a/mopidy/http/actor.py b/mopidy/http/actor.py index 57e2f46a..a61fc45c 100644 --- a/mopidy/http/actor.py +++ b/mopidy/http/actor.py @@ -129,11 +129,16 @@ class HttpServer(threading.Thread): def _get_app_request_handlers(self): result = [] for app in self.apps: + try: + request_handlers = app['factory'](self.config, self.core) + except Exception: + logger.exception('Loading %s failed.', app['name']) + continue + result.append(( r'/%s' % app['name'], handlers.AddSlashHandler )) - request_handlers = app['factory'](self.config, self.core) for handler in request_handlers: handler = list(handler) handler[0] = '/%s%s' % (app['name'], handler[0]) From 532905bd7440ec7663110db5b6fa0c86f825ff28 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 23 Dec 2014 22:20:49 +0100 Subject: [PATCH 15/21] docs: Add :discuss: shortcut for linking to forum threads --- docs/conf.py | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/conf.py b/docs/conf.py index 52e84e06..f7475293 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -156,6 +156,7 @@ extlinks = { 'commit': ('https://github.com/mopidy/mopidy/commit/%s', 'commit '), 'mpris': ( 'https://github.com/mopidy/mopidy-mpris/issues/%s', 'mopidy-mpris#'), + 'discuss': ('https://discuss.mopidy.com/t/%s', 'discuss.mopidy.com/t/'), } From aa3b8ab5f84798428134d1dda2c944e98879a168 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 23 Dec 2014 22:11:11 +0100 Subject: [PATCH 16/21] path: Support unicode content when creating file --- mopidy/utils/path.py | 6 ++++-- tests/utils/test_path.py | 12 +++++++++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/mopidy/utils/path.py b/mopidy/utils/path.py index 5870fc6e..7c1f671b 100644 --- a/mopidy/utils/path.py +++ b/mopidy/utils/path.py @@ -44,12 +44,14 @@ def get_or_create_file(file_path, mkdir=True, content=None): if not isinstance(file_path, bytes): raise ValueError('Path is not a bytestring.') file_path = expand_path(file_path) + if isinstance(content, unicode): + content = content.encode('utf-8') if mkdir: get_or_create_dir(os.path.dirname(file_path)) if not os.path.isfile(file_path): logger.info('Creating file %s', file_path) - with open(file_path, 'w') as fh: - if content: + with open(file_path, 'wb') as fh: + if content is not None: fh.write(content) return file_path diff --git a/tests/utils/test_path.py b/tests/utils/test_path.py index 078cdb20..db152654 100644 --- a/tests/utils/test_path.py +++ b/tests/utils/test_path.py @@ -105,12 +105,12 @@ class GetOrCreateFileTest(unittest.TestCase): with self.assertRaises(IOError): path.get_or_create_file(conflicting_dir) - def test_create_dir_with_unicode(self): + def test_create_dir_with_unicode_filename_throws_value_error(self): with self.assertRaises(ValueError): file_path = unicode(os.path.join(self.parent, b'test')) path.get_or_create_file(file_path) - def test_create_file_with_none(self): + def test_create_file_with_none_filename_throws_value_error(self): with self.assertRaises(ValueError): path.get_or_create_file(None) @@ -119,12 +119,18 @@ class GetOrCreateFileTest(unittest.TestCase): with self.assertRaises(IOError): path.get_or_create_file(file_path, mkdir=False) - def test_create_dir_with_default_content(self): + def test_create_dir_with_bytes_content(self): file_path = os.path.join(self.parent, b'test') created = path.get_or_create_file(file_path, content=b'foobar') with open(created) as fh: self.assertEqual(fh.read(), b'foobar') + def test_create_dir_with_unicode_content(self): + file_path = os.path.join(self.parent, b'test') + created = path.get_or_create_file(file_path, content='foobaræøå') + with open(created) as fh: + self.assertEqual(fh.read(), b'foobaræøå') + class PathToFileURITest(unittest.TestCase): def test_simple_path(self): From fcf39833ca33a107954f052cbaedcf988689d43a Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 23 Dec 2014 22:12:34 +0100 Subject: [PATCH 17/21] config: Support UTF-8 in default config Fixes issue reported at https://discuss.mopidy.com/t/428. Mopidy-HTTP-Kuechenradio includes a non-ASCII UTF-8 character in its default config. If Mopidy didn't already have a config file, it crashed when trying to create the initial config file based on the default config of all available extensions. --- docs/changelog.rst | 6 ++++++ mopidy/config/__init__.py | 8 ++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index ba00ed1f..1ef13666 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -10,6 +10,12 @@ v0.19.5 (UNRELEASED) Bug fix release. +- config: Support UTF-8 in default config. If an extension with non-ASCII + characters in its default config was installed, and Mopidy didn't already + have a config file, Mopidy would crashed when trying to create the initial + config file based on the default config of all available extensions. + (Fixes: :discuss:`428`) + - Models: Hide empty collections from :func:`repr()` representations. - Models: Field values are no longer stored on the model instance when the diff --git a/mopidy/config/__init__.py b/mopidy/config/__init__.py index 5611c717..a6578825 100644 --- a/mopidy/config/__init__.py +++ b/mopidy/config/__init__.py @@ -97,8 +97,12 @@ def format_initial(extensions): versions = ['Mopidy %s' % versioning.get_version()] for extension in sorted(extensions, key=lambda ext: ext.dist_name): versions.append('%s %s' % (extension.dist_name, extension.version)) - description = _INITIAL_HELP.strip() % {'versions': '\n# '.join(versions)} - return description + '\n\n' + _format(config, {}, schemas, False, True) + + header = _INITIAL_HELP.strip() % {'versions': '\n# '.join(versions)} + formatted_config = _format( + config=config, comments={}, schemas=schemas, + display=False, disable=True).decode('utf-8') + return header + '\n\n' + formatted_config def _load(files, defaults, overrides): From 302bb7c2218331d8de32375138794060bb9e5b4c Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 23 Dec 2014 22:58:17 +0100 Subject: [PATCH 18/21] ext: Fix unpacking of VersionConflict exception Fixes #911 --- docs/changelog.rst | 6 +++++- mopidy/ext.py | 11 +++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 1ef13666..19257e46 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -10,12 +10,16 @@ v0.19.5 (UNRELEASED) Bug fix release. -- config: Support UTF-8 in default config. If an extension with non-ASCII +- Config: Support UTF-8 in default config. If an extension with non-ASCII characters in its default config was installed, and Mopidy didn't already have a config file, Mopidy would crashed when trying to create the initial config file based on the default config of all available extensions. (Fixes: :discuss:`428`) +- Extensions: Fix crash when unpacking data from + :exc:`pkg_resources.VersionConflict` created with a single argument. (Fixes: + :issue:`911`) + - Models: Hide empty collections from :func:`repr()` representations. - Models: Field values are no longer stored on the model instance when the diff --git a/mopidy/ext.py b/mopidy/ext.py index 3333ec3f..24d85786 100644 --- a/mopidy/ext.py +++ b/mopidy/ext.py @@ -188,10 +188,13 @@ def validate_extension(extension): 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) + if len(ex.args) == 2: + found, required = ex.args + logger.info( + 'Disabled extension %s: %s required, but found %s at %s', + extension.ext_name, required, found, found.location) + else: + logger.info('Disabled extension %s: %s', extension.ext_name, ex) return False try: From c6ff9eee86404aae2dc8afd28e7b9481eaf98228 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 23 Dec 2014 23:58:20 +0100 Subject: [PATCH 19/21] playback: Remove skipped track on next in consume mode Also adds core level tests of consume behavior on next/prev/eot. Fixes #902 --- docs/changelog.rst | 4 +++ mopidy/core/playback.py | 10 ++++--- mopidy/core/tracklist.py | 8 +++--- tests/core/test_playback.py | 51 ++++++++++++++++++++++++++++++++++++ tests/local/test_playback.py | 2 +- 5 files changed, 67 insertions(+), 8 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 19257e46..4e3662f1 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -32,6 +32,10 @@ Bug fix release. :attr:`mopidy.models.Track.track_no`, and :attr:`mopidy.models.Track.last_modified` from ``0`` to :class:`None`. +- Core: When skipping to the next track in consume mode, remove the skipped + track from the tracklist. This is consistent with the original MPD server's + behavior. (Fixes: :issue:`902`) + - Local: Fix scanning of modified files. (PR: :issue:`904`) - MPD: Re-enable browsing of empty directories. (PR: :issue:`906`) diff --git a/mopidy/core/playback.py b/mopidy/core/playback.py index 097a9401..d6307e2f 100644 --- a/mopidy/core/playback.py +++ b/mopidy/core/playback.py @@ -179,12 +179,16 @@ class PlaybackController(object): The current playback state will be kept. If it was playing, playing will continue. If it was paused, it will still be paused, etc. """ - tl_track = self.core.tracklist.next_track(self.current_tl_track) - if tl_track: - self.change_track(tl_track) + original_tl_track = self.current_tl_track + next_tl_track = self.core.tracklist.next_track(original_tl_track) + + if next_tl_track: + self.change_track(next_tl_track) else: self.stop(clear_current_track=True) + self.core.tracklist.mark_played(original_tl_track) + def pause(self): """Pause playback.""" backend = self._get_backend() diff --git a/mopidy/core/tracklist.py b/mopidy/core/tracklist.py index 4c567086..80ed5aea 100644 --- a/mopidy/core/tracklist.py +++ b/mopidy/core/tracklist.py @@ -448,10 +448,10 @@ class TracklistController(object): def mark_played(self, tl_track): """Private method used by :class:`mopidy.core.PlaybackController`.""" - if not self.consume: - return False - self.remove(tlid=[tl_track.tlid]) - return True + if self.consume and tl_track is not None: + self.remove(tlid=[tl_track.tlid]) + return True + return False def _trigger_tracklist_changed(self): if self.random: diff --git a/tests/core/test_playback.py b/tests/core/test_playback.py index ce6c8571..909ca4fa 100644 --- a/tests/core/test_playback.py +++ b/tests/core/test_playback.py @@ -239,6 +239,23 @@ class CorePlaybackTest(unittest.TestCase): # TODO Test next() more + def test_next_keeps_finished_track_in_tracklist(self): + tl_track = self.tl_tracks[0] + self.core.playback.play(tl_track) + + self.core.playback.next() + + self.assertIn(tl_track, self.core.tracklist.tl_tracks) + + def test_next_in_consume_mode_removes_finished_track(self): + tl_track = self.tl_tracks[0] + self.core.playback.play(tl_track) + self.core.tracklist.consume = True + + self.core.playback.next() + + self.assertNotIn(tl_track, self.core.tracklist.tl_tracks) + @mock.patch( 'mopidy.core.playback.listener.CoreListener', spec=core.CoreListener) def test_next_emits_events(self, listener_mock): @@ -265,6 +282,23 @@ class CorePlaybackTest(unittest.TestCase): # TODO Test previous() more + def test_previous_keeps_finished_track_in_tracklist(self): + tl_track = self.tl_tracks[1] + self.core.playback.play(tl_track) + + self.core.playback.previous() + + self.assertIn(tl_track, self.core.tracklist.tl_tracks) + + def test_previous_keeps_finished_track_even_in_consume_mode(self): + tl_track = self.tl_tracks[1] + self.core.playback.play(tl_track) + self.core.tracklist.consume = True + + self.core.playback.previous() + + self.assertIn(tl_track, self.core.tracklist.tl_tracks) + @mock.patch( 'mopidy.core.playback.listener.CoreListener', spec=core.CoreListener) def test_previous_emits_events(self, listener_mock): @@ -291,6 +325,23 @@ class CorePlaybackTest(unittest.TestCase): # TODO Test on_end_of_track() more + def test_on_end_of_track_keeps_finished_track_in_tracklist(self): + tl_track = self.tl_tracks[0] + self.core.playback.play(tl_track) + + self.core.playback.on_end_of_track() + + self.assertIn(tl_track, self.core.tracklist.tl_tracks) + + def test_on_end_of_track_in_consume_mode_removes_finished_track(self): + tl_track = self.tl_tracks[0] + self.core.playback.play(tl_track) + self.core.tracklist.consume = True + + self.core.playback.on_end_of_track() + + self.assertNotIn(tl_track, self.core.tracklist.tl_tracks) + @mock.patch( 'mopidy.core.playback.listener.CoreListener', spec=core.CoreListener) def test_on_end_of_track_emits_events(self, listener_mock): diff --git a/tests/local/test_playback.py b/tests/local/test_playback.py index 7b64e495..89015739 100644 --- a/tests/local/test_playback.py +++ b/tests/local/test_playback.py @@ -347,7 +347,7 @@ class LocalPlaybackProviderTest(unittest.TestCase): self.tracklist.consume = True self.playback.play() self.playback.next() - self.assertIn(self.tracks[0], self.tracklist.tracks) + self.assertNotIn(self.tracks[0], self.tracklist.tracks) @populate_tracklist def test_next_with_single_and_repeat(self): From 884c35e1f4169a8855a82f99bdb3a46ed4916a65 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Wed, 24 Dec 2014 00:36:52 +0100 Subject: [PATCH 20/21] docs: Update changelog --- docs/changelog.rst | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 4e3662f1..1b88c489 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -5,15 +5,16 @@ Changelog This changelog is used to track all major changes to Mopidy. -v0.19.5 (UNRELEASED) +v0.19.5 (2014-12-23) ==================== -Bug fix release. +Today is Mopidy's five year anniversary. We're celebrating with a bugfix +release and are looking forward to the next five years! -- Config: Support UTF-8 in default config. If an extension with non-ASCII - characters in its default config was installed, and Mopidy didn't already - have a config file, Mopidy would crashed when trying to create the initial - config file based on the default config of all available extensions. +- Config: Support UTF-8 in extension's default config. If an extension with + non-ASCII characters in its default config was installed, and Mopidy didn't + already have a config file, Mopidy would crashed when trying to create the + initial config file based on the default config of all available extensions. (Fixes: :discuss:`428`) - Extensions: Fix crash when unpacking data from From 6af624a3699df40369c1b95cff5a4c4eab51adaf Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Wed, 24 Dec 2014 00:37:33 +0100 Subject: [PATCH 21/21] Bump version to 0.19.5 --- 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 7b55f20a..6c122057 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.4' +__version__ = '0.19.5' diff --git a/tests/test_version.py b/tests/test_version.py index 96063a1b..7e3922ca 100644 --- a/tests/test_version.py +++ b/tests/test_version.py @@ -50,5 +50,6 @@ class VersionTest(unittest.TestCase): self.assertLess(SV('0.19.0'), SV('0.19.1')) self.assertLess(SV('0.19.1'), SV('0.19.2')) self.assertLess(SV('0.19.2'), SV('0.19.3')) - self.assertLess(SV('0.19.3'), SV(__version__)) - self.assertLess(SV(__version__), SV('0.19.5')) + self.assertLess(SV('0.19.3'), SV('0.19.4')) + self.assertLess(SV('0.19.4'), SV(__version__)) + self.assertLess(SV(__version__), SV('0.19.6'))