diff --git a/.travis.yml b/.travis.yml index 6120e2de..bbba0a94 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,3 +17,5 @@ notifications: - "irc.freenode.org#mopidy" on_success: change on_failure: change + use_notice: true + skip_join: true diff --git a/docs/changes.rst b/docs/changes.rst index ef0d3bcd..ea5a1530 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -86,6 +86,16 @@ backends: - The Spotify backend now includes release year and artist on albums. +- Added support for search by filename to local backend. + +**Bug fixes** + +- :issue:`218`: The MPD commands ``listplaylist`` and ``listplaylistinfo`` now + accepts unquotes playlist names if they don't contain spaces. + +- The MPD command ``plchanges`` always returned the entire playlist. It now + returns an empty response when the client has seen the latest version. + v0.8.1 (2012-10-30) =================== diff --git a/mopidy/backends/base.py b/mopidy/backends/base.py index 95cd93c4..de33e6e5 100644 --- a/mopidy/backends/base.py +++ b/mopidy/backends/base.py @@ -70,6 +70,8 @@ class BaseLibraryProvider(object): class BasePlaybackProvider(object): """ + :param audio: the audio actor + :type audio: actor proxy to an instance of :class:`mopidy.audio.Audio` :param backend: the backend :type backend: :class:`mopidy.backends.base.Backend` """ diff --git a/mopidy/backends/local/library.py b/mopidy/backends/local/library.py index 600bfaaa..9abdf7ed 100644 --- a/mopidy/backends/local/library.py +++ b/mopidy/backends/local/library.py @@ -59,7 +59,7 @@ class LocalLibraryProvider(base.BaseLibraryProvider): result_tracks = filter(album_filter, result_tracks) elif field == 'artist': result_tracks = filter(artist_filter, result_tracks) - elif field == 'uri': + elif field in ('uri', 'filename'): result_tracks = filter(uri_filter, result_tracks) elif field == 'any': result_tracks = filter(any_filter, result_tracks) @@ -93,7 +93,7 @@ class LocalLibraryProvider(base.BaseLibraryProvider): result_tracks = filter(album_filter, result_tracks) elif field == 'artist': result_tracks = filter(artist_filter, result_tracks) - elif field == 'uri': + elif field in ('uri', 'filename'): result_tracks = filter(uri_filter, result_tracks) elif field == 'any': result_tracks = filter(any_filter, result_tracks) diff --git a/mopidy/frontends/mpd/protocol/current_playlist.py b/mopidy/frontends/mpd/protocol/current_playlist.py index 429af2cc..5a88d41b 100644 --- a/mopidy/frontends/mpd/protocol/current_playlist.py +++ b/mopidy/frontends/mpd/protocol/current_playlist.py @@ -307,7 +307,7 @@ def plchanges(context, version): - Calls ``plchanges "-1"`` two times per second to get the entire playlist. """ # XXX Naive implementation that returns all tracks as changed - if int(version) < context.core.current_playlist.version: + if int(version) < context.core.current_playlist.version.get(): return translator.tracks_to_mpd_format( context.core.current_playlist.cp_tracks.get()) diff --git a/mopidy/frontends/mpd/protocol/stored_playlists.py b/mopidy/frontends/mpd/protocol/stored_playlists.py index ed1c38ab..17e5abf7 100644 --- a/mopidy/frontends/mpd/protocol/stored_playlists.py +++ b/mopidy/frontends/mpd/protocol/stored_playlists.py @@ -5,6 +5,7 @@ from mopidy.frontends.mpd.protocol import handle_request from mopidy.frontends.mpd.translator import playlist_to_mpd_format +@handle_request(r'^listplaylist (?P\S+)$') @handle_request(r'^listplaylist "(?P[^"]+)"$') def listplaylist(context, name): """ @@ -27,6 +28,7 @@ def listplaylist(context, name): raise MpdNoExistError(u'No such playlist', command=u'listplaylist') +@handle_request(r'^listplaylistinfo (?P\S+)$') @handle_request(r'^listplaylistinfo "(?P[^"]+)"$') def listplaylistinfo(context, name): """ diff --git a/mopidy/frontends/mpd/session.py b/mopidy/frontends/mpd/session.py index b5368a08..5d535f75 100644 --- a/mopidy/frontends/mpd/session.py +++ b/mopidy/frontends/mpd/session.py @@ -25,17 +25,14 @@ class MpdSession(network.LineProtocol): self.send_lines([u'OK MPD %s' % protocol.VERSION]) def on_line_received(self, line): - logger.debug( - u'Request from [%s]:%s to %s: %s', - self.host, self.port, self.actor_urn, line) + logger.debug(u'Request from [%s]:%s: %s', self.host, self.port, line) response = self.dispatcher.handle_request(line) if not response: return logger.debug( - u'Response to [%s]:%s from %s: %s', - self.host, self.port, self.actor_urn, + u'Response to [%s]:%s: %s', self.host, self.port, formatting.indent(self.terminator.join(response))) self.send_lines(response) diff --git a/requirements/tests.txt b/requirements/tests.txt index e24edd3c..20aff929 100644 --- a/requirements/tests.txt +++ b/requirements/tests.txt @@ -1,6 +1,8 @@ coverage +flake8 mock >= 0.7 nose +pylint tox unittest2 yappi diff --git a/tests/backends/base/library.py b/tests/backends/base/library.py index cc2a0004..b7510dbb 100644 --- a/tests/backends/base/library.py +++ b/tests/backends/base/library.py @@ -79,6 +79,15 @@ class LibraryControllerTest(object): result = self.library.find_exact(album=['album2']) self.assertEqual(result, Playlist(tracks=self.tracks[1:2])) + def test_find_exact_filename(self): + track_1_filename = 'file://' + path_to_data_dir('uri1') + result = self.library.find_exact(filename=track_1_filename) + self.assertEqual(result, Playlist(tracks=self.tracks[:1])) + + track_2_filename = 'file://' + path_to_data_dir('uri2') + result = self.library.find_exact(filename=track_2_filename) + self.assertEqual(result, Playlist(tracks=self.tracks[1:2])) + def test_find_exact_wrong_type(self): test = lambda: self.library.find_exact(wrong=['test']) self.assertRaises(LookupError, test) @@ -137,6 +146,13 @@ class LibraryControllerTest(object): result = self.library.search(uri=['RI2']) self.assertEqual(result, Playlist(tracks=self.tracks[1:2])) + def test_search_filename(self): + result = self.library.search(filename=['RI1']) + self.assertEqual(result, Playlist(tracks=self.tracks[:1])) + + result = self.library.search(filename=['RI2']) + self.assertEqual(result, Playlist(tracks=self.tracks[1:2])) + def test_search_any(self): result = self.library.search(any=['Tist1']) self.assertEqual(result, Playlist(tracks=self.tracks[:1])) diff --git a/tests/frontends/mpd/protocol/current_playlist_test.py b/tests/frontends/mpd/protocol/current_playlist_test.py index a64b08ea..bd58cf2d 100644 --- a/tests/frontends/mpd/protocol/current_playlist_test.py +++ b/tests/frontends/mpd/protocol/current_playlist_test.py @@ -364,7 +364,7 @@ class CurrentPlaylistHandlerTest(protocol.BaseTestCase): self.sendRequest(u'playlistsearch any "needle"') self.assertEqualResponse(u'ACK [0@0] {} Not implemented') - def test_plchanges(self): + def test_plchanges_with_lower_version_returns_changes(self): self.core.current_playlist.append( [Track(name='a'), Track(name='b'), Track(name='c')]) @@ -374,6 +374,28 @@ class CurrentPlaylistHandlerTest(protocol.BaseTestCase): self.assertInResponse(u'Title: c') self.assertInResponse(u'OK') + def test_plchanges_with_equal_version_returns_nothing(self): + self.core.current_playlist.append( + [Track(name='a'), Track(name='b'), Track(name='c')]) + + self.assertEqual(self.core.current_playlist.version.get(), 1) + self.sendRequest(u'plchanges "1"') + self.assertNotInResponse(u'Title: a') + self.assertNotInResponse(u'Title: b') + self.assertNotInResponse(u'Title: c') + self.assertInResponse(u'OK') + + def test_plchanges_with_greater_version_returns_nothing(self): + self.core.current_playlist.append( + [Track(name='a'), Track(name='b'), Track(name='c')]) + + self.assertEqual(self.core.current_playlist.version.get(), 1) + self.sendRequest(u'plchanges "2"') + self.assertNotInResponse(u'Title: a') + self.assertNotInResponse(u'Title: b') + self.assertNotInResponse(u'Title: c') + self.assertInResponse(u'OK') + def test_plchanges_with_minus_one_returns_entire_playlist(self): self.core.current_playlist.append( [Track(name='a'), Track(name='b'), Track(name='c')]) diff --git a/tests/frontends/mpd/protocol/stored_playlists_test.py b/tests/frontends/mpd/protocol/stored_playlists_test.py index 346cd37f..8cfe237c 100644 --- a/tests/frontends/mpd/protocol/stored_playlists_test.py +++ b/tests/frontends/mpd/protocol/stored_playlists_test.py @@ -14,6 +14,14 @@ class StoredPlaylistsHandlerTest(protocol.BaseTestCase): self.assertInResponse(u'file: file:///dev/urandom') self.assertInResponse(u'OK') + def test_listplaylist_without_quotes(self): + self.core.stored_playlists.playlists = [ + Playlist(name='name', tracks=[Track(uri='file:///dev/urandom')])] + + self.sendRequest(u'listplaylist name') + self.assertInResponse(u'file: file:///dev/urandom') + self.assertInResponse(u'OK') + def test_listplaylist_fails_if_no_playlist_is_found(self): self.sendRequest(u'listplaylist "name"') self.assertEqualResponse(u'ACK [50@0] {listplaylist} No such playlist') @@ -28,6 +36,16 @@ class StoredPlaylistsHandlerTest(protocol.BaseTestCase): self.assertNotInResponse(u'Pos: 0') self.assertInResponse(u'OK') + def test_listplaylistinfo_without_quotes(self): + self.core.stored_playlists.playlists = [ + Playlist(name='name', tracks=[Track(uri='file:///dev/urandom')])] + + self.sendRequest(u'listplaylistinfo name') + self.assertInResponse(u'file: file:///dev/urandom') + self.assertInResponse(u'Track: 0') + self.assertNotInResponse(u'Pos: 0') + self.assertInResponse(u'OK') + def test_listplaylistinfo_fails_if_no_playlist_is_found(self): self.sendRequest(u'listplaylistinfo "name"') self.assertEqualResponse(