diff --git a/docs/changes.rst b/docs/changes.rst index b9981c53..a4bb002e 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -15,6 +15,10 @@ Another great release. - Exit early if not Python >= 2.6, < 3. - Include Sphinx scripts for building docs, pylintrc, tests and test data in the packages created by ``setup.py`` for i.e. PyPI. +- Backend API: + + - The ``id`` field of :class:`mopidy.models.Track` has been removed, as it is + no longer needed after the CPID refactoring. 0.1.0a3 (2010-08-03) diff --git a/mopidy/backends/despotify.py b/mopidy/backends/despotify.py index 05a95fb5..17281ab5 100644 --- a/mopidy/backends/despotify.py +++ b/mopidy/backends/despotify.py @@ -9,7 +9,6 @@ from mopidy.backends import (BaseBackend, BaseCurrentPlaylistController, BaseLibraryController, BasePlaybackController, BaseStoredPlaylistsController) from mopidy.models import Artist, Album, Track, Playlist -from mopidy.utils import spotify_uri_to_int logger = logging.getLogger('mopidy.backends.despotify') @@ -128,10 +127,6 @@ class DespotifyStoredPlaylistsController(BaseStoredPlaylistsController): class DespotifyTranslator(object): - @classmethod - def to_mopidy_id(cls, spotify_uri): - return spotify_uri_to_int(spotify_uri) - @classmethod def to_mopidy_artist(cls, spotify_artist): return Artist( @@ -160,7 +155,6 @@ class DespotifyTranslator(object): date=date, length=spotify_track.length, bitrate=320, - id=cls.to_mopidy_id(spotify_track.get_uri()), ) @classmethod diff --git a/mopidy/backends/libspotify.py b/mopidy/backends/libspotify.py index 08459243..4f1b2049 100644 --- a/mopidy/backends/libspotify.py +++ b/mopidy/backends/libspotify.py @@ -13,7 +13,6 @@ from mopidy.backends import (BaseBackend, BaseCurrentPlaylistController, BaseLibraryController, BasePlaybackController, BaseStoredPlaylistsController) from mopidy.models import Artist, Album, Track, Playlist -from mopidy.utils import spotify_uri_to_int import alsaaudio @@ -39,8 +38,7 @@ class LibspotifyBackend(BaseBackend): def __init__(self, *args, **kwargs): super(LibspotifyBackend, self).__init__(*args, **kwargs) - self.current_playlist = LibspotifyCurrentPlaylistController( - backend=self) + self.current_playlist = BaseCurrentPlaylistController(backend=self) self.library = LibspotifyLibraryController(backend=self) self.playback = LibspotifyPlaybackController(backend=self) self.stored_playlists = LibspotifyStoredPlaylistsController( @@ -60,15 +58,17 @@ class LibspotifyBackend(BaseBackend): return spotify -class LibspotifyCurrentPlaylistController(BaseCurrentPlaylistController): - pass - - class LibspotifyLibraryController(BaseLibraryController): + def find_exact(self, **query): + return self.search(**query) + def lookup(self, uri): spotify_track = Link.from_string(uri).as_track() return LibspotifyTranslator.to_mopidy_track(spotify_track) + def refresh(self, uri=None): + pass # TODO + def search(self, **query): spotify_query = [] for (field, values) in query.iteritems(): @@ -85,14 +85,6 @@ class LibspotifyLibraryController(BaseLibraryController): logger.debug(u'In search method, search for: %s' % spotify_query) my_end, other_end = multiprocessing.Pipe() self.backend.spotify.search(spotify_query.encode(ENCODING), other_end) - my_end.poll(None) - logger.debug(u'In search method, receiving search results') - playlist = my_end.recv() - logger.debug(u'In search method, done receiving search results') - logger.debug(['%s' % t.name for t in playlist.tracks]) - return playlist - - find_exact = search class LibspotifyPlaybackController(BasePlaybackController): @@ -118,20 +110,38 @@ class LibspotifyPlaybackController(BasePlaybackController): # TODO return False + def _seek(self, time_position): + pass # TODO + def _stop(self): self.backend.spotify.session.play(0) return True class LibspotifyStoredPlaylistsController(BaseStoredPlaylistsController): - pass + def create(self, name): + pass # TODO + + def delete(self, playlist): + pass # TODO + + def lookup(self, uri): + pass # TODO + + def refresh(self): + pass # TODO + + def rename(self, playlist, new_name): + pass # TODO + + def save(self, playlist): + pass # TODO + + def search(self, query): + pass # TODO class LibspotifyTranslator(object): - @classmethod - def to_mopidy_id(cls, spotify_uri): - return spotify_uri_to_int(spotify_uri) - @classmethod def to_mopidy_artist(cls, spotify_artist): if not spotify_artist.is_loaded(): @@ -166,7 +176,6 @@ class LibspotifyTranslator(object): date=date, length=spotify_track.duration(), bitrate=320, - id=cls.to_mopidy_id(uri), ) @classmethod @@ -253,15 +262,25 @@ class LibspotifySessionManager(SpotifySessionManager, threading.Thread): def search(self, query, connection): """Search method used by Mopidy backend""" - self.connected.wait() def callback(results, userdata): - logger.debug(u'In search callback, translating search results') + logger.debug(u'In SessionManager.search().callback(), ' + 'translating search results') logger.debug(results.tracks()) # TODO Include results from results.albums(), etc. too playlist = Playlist(tracks=[ LibspotifyTranslator.to_mopidy_track(t) for t in results.tracks()]) - logger.debug(u'In search callback, sending search results') + logger.debug(u'In SessionManager.search().callback(), ' + 'sending search results') logger.debug(['%s' % t.name for t in playlist.tracks]) connection.send(playlist) + logger.debug(u'In SessionManager.search().callback(), ' + 'done sending search results') + logger.debug(u'In SessionManager.search(), ' + 'waiting for Spotify connection') + self.connected.wait() + logger.debug(u'In SessionManager.search(), ' + 'sending search query') self.session.search(query, callback) + logger.debug(u'In SessionManager.search(), ' + 'done sending search query') diff --git a/mopidy/models.py b/mopidy/models.py index 09a509dc..5a75e620 100644 --- a/mopidy/models.py +++ b/mopidy/models.py @@ -103,8 +103,6 @@ class Track(ImmutableObject): :type length: integer :param bitrate: bitrate in kbit/s :type bitrate: integer - :param id: track ID (unique and non-changing as long as the process lives) - :type id: integer """ #: The track URI. Read-only. @@ -128,9 +126,6 @@ class Track(ImmutableObject): #: The track's bitrate in kbit/s. Read-only. bitrate = None - #: The track ID. Read-only. - id = None - def __init__(self, *args, **kwargs): self._artists = frozenset(kwargs.pop('artists', [])) super(Track, self).__init__(*args, **kwargs) diff --git a/mopidy/utils.py b/mopidy/utils.py index 758fe2e3..077f004e 100644 --- a/mopidy/utils.py +++ b/mopidy/utils.py @@ -58,52 +58,6 @@ def unpickle_connection(pickled_connection): (func, args) = pickle.loads(pickled_connection) return func(*args) -def spotify_uri_to_int(uri, output_bits=31): - """ - Stable one-way translation from Spotify URI to 31-bit integer. - - Spotify track URIs has 62^22 possible values, which requires 131 bits of - storage. The original MPD server uses 32-bit unsigned integers for track - IDs. GMPC seems to think the track ID is a signed integer, thus we use 31 - output bits. - - In other words, this function throws away 100 bits of information. Since we - only use the track IDs to identify a track within a single Mopidy instance, - this information loss is acceptable. The chances of getting two different - tracks with the same track ID loaded in the same Mopidy instance is still - rather slim. 1 to 2,147,483,648 to be exact. - - Normal usage, with data loss:: - - >>> spotify_uri_to_int('spotify:track:5KRRcT67VNIZUygEbMoIC1') - 624351954 - - No data loss, may be converted back into a Spotify URI:: - - >>> spotify_uri_to_int('spotify:track:5KRRcT67VNIZUygEbMoIC1', - ... output_bits=131) - 101411513484007705241035418492696638725L - - :param uri: Spotify URI on the format ``spotify:track:*`` - :type uri: string - :param output_bits: number of bits of information kept in the return value - :type output_bits: int - :rtype: int - """ - - CHARS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' - BITS_PER_CHAR = 6 # int(math.ceil(math.log(len(CHARS), 2))) - - key = uri.split(':')[-1] - full_id = 0 - for i, char in enumerate(key): - full_id ^= CHARS.index(char) << BITS_PER_CHAR * i - compressed_id = 0 - while full_id != 0: - compressed_id ^= (full_id & (2 ** output_bits - 1)) - full_id >>= output_bits - return int(compressed_id) - def parse_m3u(file_path): """ Convert M3U file list of uris @@ -219,7 +173,6 @@ def _convert_mpd_data(data, tracks, music_dir): track_kwargs['uri'] = path_to_uri(music_dir, path) track_kwargs['length'] = int(data.get('time', 0)) * 1000 - track_kwargs['id'] = len(tracks) track = Track(**track_kwargs) tracks.add(track) diff --git a/tests/backends/base.py b/tests/backends/base.py index 59d03b8d..ecb4c693 100644 --- a/tests/backends/base.py +++ b/tests/backends/base.py @@ -65,22 +65,13 @@ class BaseCurrentPlaylistControllerTest(object): cp_track = self.controller.cp_tracks[1] self.assertEqual(cp_track, self.controller.get(cpid=cp_track[0])) - @populate_playlist - def test_get_by_id(self): - cp_track = self.controller.cp_tracks[1] - self.assertEqual(cp_track, self.controller.get(id=cp_track[1].id)) - - @populate_playlist - def test_get_by_id_raises_error_for_invalid_id(self): - self.assertRaises(LookupError, lambda: self.controller.get(id=1337)) - @populate_playlist def test_get_by_uri(self): cp_track = self.controller.cp_tracks[1] self.assertEqual(cp_track, self.controller.get(uri=cp_track[1].uri)) @populate_playlist - def test_get_by_uri_raises_error_for_invalid_id(self): + def test_get_by_uri_raises_error_for_invalid_uri(self): test = lambda: self.controller.get(uri='foobar') self.assertRaises(LookupError, test) @@ -106,28 +97,6 @@ class BaseCurrentPlaylistControllerTest(object): self.controller.load(tracks) self.assertEqual(tracks, self.controller.tracks) - def test_get_by_id_returns_unique_match(self): - track = Track(id=1) - self.controller.load([Track(id=13), track, Track(id=17)]) - self.assertEqual(track, self.controller.get(id=1)[1]) - - def test_get_by_id_raises_error_if_multiple_matches(self): - track = Track(id=1) - self.controller.load([Track(id=13), track, track]) - try: - self.controller.get(id=1) - self.fail(u'Should raise LookupError if multiple matches') - except LookupError as e: - self.assertEqual(u'"id=1" match multiple tracks', e[0]) - - def test_get_by_id_raises_error_if_no_match(self): - self.controller.playlist = Playlist(tracks=[Track(id=13), Track(id=17)]) - try: - self.controller.get(id=1) - self.fail(u'Should raise LookupError if no match') - except LookupError as e: - self.assertEqual(u'"id=1" match no tracks', e[0]) - def test_get_by_uri_returns_unique_match(self): track = Track(uri='a') self.controller.load([Track(uri='z'), track, Track(uri='y')]) @@ -152,20 +121,20 @@ class BaseCurrentPlaylistControllerTest(object): self.assertEqual(u'"uri=a" match no tracks', e[0]) def test_get_by_multiple_criteria_returns_elements_matching_all(self): - track1 = Track(id=1, uri='a') - track2 = Track(id=1, uri='b') - track3 = Track(id=2, uri='b') + track1 = Track(uri='a', name='x') + track2 = Track(uri='b', name='x') + track3 = Track(uri='b', name='y') self.controller.load([track1, track2, track3]) - self.assertEqual(track1, self.controller.get(id=1, uri='a')[1]) - self.assertEqual(track2, self.controller.get(id=1, uri='b')[1]) - self.assertEqual(track3, self.controller.get(id=2, uri='b')[1]) + self.assertEqual(track1, self.controller.get(uri='a', name='x')[1]) + self.assertEqual(track2, self.controller.get(uri='b', name='x')[1]) + self.assertEqual(track3, self.controller.get(uri='b', name='y')[1]) def test_get_by_criteria_that_is_not_present_in_all_elements(self): - track1 = Track(id=1) + track1 = Track() track2 = Track(uri='b') - track3 = Track(id=2) + track3 = Track() self.controller.load([track1, track2, track3]) - self.assertEqual(track1, self.controller.get(id=1)[1]) + self.assertEqual(track2, self.controller.get(uri='b')[1]) @populate_playlist def test_load_replaces_playlist(self): @@ -244,18 +213,18 @@ class BaseCurrentPlaylistControllerTest(object): track1 = self.controller.tracks[1] track2 = self.controller.tracks[2] version = self.controller.version - self.controller.remove(id=track1.id) + self.controller.remove(uri=track1.uri) self.assert_(version < self.controller.version) self.assert_(track1 not in self.controller.tracks) self.assertEqual(track2, self.controller.tracks[1]) @populate_playlist def test_removing_track_that_does_not_exist(self): - test = lambda: self.controller.remove(id=12345) + test = lambda: self.controller.remove(uri='/nonexistant') self.assertRaises(LookupError, test) def test_removing_from_empty_playlist(self): - test = lambda: self.controller.remove(id=12345) + test = lambda: self.controller.remove(uri='/nonexistant') self.assertRaises(LookupError, test) @populate_playlist @@ -1025,7 +994,7 @@ class BaseStoredPlaylistsControllerTest(object): except LookupError as e: self.assertEqual(u'"name=b" match multiple playlists', e[0]) - def test_get_by_id_raises_keyerror_if_no_match(self): + def test_get_by_name_raises_keyerror_if_no_match(self): self.stored.playlists = [Playlist(name='a'), Playlist(name='b')] try: self.stored.get(name='c') @@ -1080,10 +1049,10 @@ class BaseLibraryControllerTest(object): Album(name='album2', artists=artists[1:2]), Album()] tracks = [Track(name='track1', length=4000, artists=artists[:1], - album=albums[0], uri='file://' + data_folder('uri1'), id=0), + album=albums[0], uri='file://' + data_folder('uri1')), Track(name='track2', length=4000, artists=artists[1:2], - album=albums[1], uri='file://' + data_folder('uri2'), id=1), - Track(id=3)] + album=albums[1], uri='file://' + data_folder('uri2')), + Track()] def setUp(self): self.backend = self.backend_class(mixer=DummyMixer()) diff --git a/tests/backends/despotify_integrationtest.py b/tests/backends/despotify_integrationtest.py index 1d960f77..4192bf7b 100644 --- a/tests/backends/despotify_integrationtest.py +++ b/tests/backends/despotify_integrationtest.py @@ -15,13 +15,13 @@ uris = [ class DespotifyCurrentPlaylistControllerTest( BaseCurrentPlaylistControllerTest, unittest.TestCase): - tracks = [Track(uri=uri, id=i, length=4464) for i, uri in enumerate(uris)] + tracks = [Track(uri=uri, length=4464) for i, uri in enumerate(uris)] backend_class = DespotifyBackend class DespotifyPlaybackControllerTest( BasePlaybackControllerTest, unittest.TestCase): - tracks = [Track(uri=uri, id=i, length=4464) for i, uri in enumerate(uris)] + tracks = [Track(uri=uri, length=4464) for i, uri in enumerate(uris)] backend_class = DespotifyBackend diff --git a/tests/backends/gstreamer_test.py b/tests/backends/gstreamer_test.py index 93c5eb48..22619c80 100644 --- a/tests/backends/gstreamer_test.py +++ b/tests/backends/gstreamer_test.py @@ -23,7 +23,7 @@ generate_song = lambda i: path_to_uri(song % i) # FIXME can be switched to generic test class GStreamerCurrentPlaylistControllerTest(BaseCurrentPlaylistControllerTest, unittest.TestCase): - tracks = [Track(uri=generate_song(i), id=i, length=4464) + tracks = [Track(uri=generate_song(i), length=4464) for i in range(1, 4)] backend_class = GStreamerBackend @@ -31,7 +31,7 @@ class GStreamerCurrentPlaylistControllerTest(BaseCurrentPlaylistControllerTest, class GStreamerPlaybackControllerTest(BasePlaybackControllerTest, unittest.TestCase): - tracks = [Track(uri=generate_song(i), id=i, length=4464) + tracks = [Track(uri=generate_song(i), length=4464) for i in range(1, 4)] backend_class = GStreamerBackend @@ -42,7 +42,7 @@ class GStreamerPlaybackControllerTest(BasePlaybackControllerTest, def add_track(self, path): uri = path_to_uri(data_folder(path)) - track = Track(uri=uri, id=1, length=4464) + track = Track(uri=uri, length=4464) self.backend.current_playlist.add(track) def test_uri_handler(self): diff --git a/tests/backends/libspotify_integrationtest.py b/tests/backends/libspotify_integrationtest.py index 6e4916f5..1e7e9b97 100644 --- a/tests/backends/libspotify_integrationtest.py +++ b/tests/backends/libspotify_integrationtest.py @@ -15,13 +15,13 @@ uris = [ class LibspotifyCurrentPlaylistControllerTest( BaseCurrentPlaylistControllerTest, unittest.TestCase): - tracks = [Track(uri=uri, id=i, length=4464) for i, uri in enumerate(uris)] + tracks = [Track(uri=uri, length=4464) for i, uri in enumerate(uris)] backend_class = LibspotifyBackend class LibspotifyPlaybackControllerTest( BasePlaybackControllerTest, unittest.TestCase): - tracks = [Track(uri=uri, id=i, length=4464) for i, uri in enumerate(uris)] + tracks = [Track(uri=uri, length=4464) for i, uri in enumerate(uris)] backend_class = LibspotifyBackend diff --git a/tests/models_test.py b/tests/models_test.py index 7fab86f5..ab7bc793 100644 --- a/tests/models_test.py +++ b/tests/models_test.py @@ -222,12 +222,6 @@ class TrackTest(unittest.TestCase): self.assertEqual(track.bitrate, bitrate) self.assertRaises(AttributeError, setattr, track, 'bitrate', None) - def test_id(self): - track_id = 17 - track = Track(id=track_id) - self.assertEqual(track.id, track_id) - self.assertRaises(AttributeError, setattr, track, 'id', None) - def test_invalid_kwarg(self): test = lambda: Track(foo='baz') self.assertRaises(TypeError, test) @@ -291,20 +285,14 @@ class TrackTest(unittest.TestCase): self.assertEqual(track1, track2) self.assertEqual(hash(track1), hash(track2)) - def test_eq_id(self): - track1 = Track(id=100) - track2 = Track(id=100) - self.assertEqual(track1, track2) - self.assertEqual(hash(track1), hash(track2)) - def test_eq(self): date = dt.date.today() artists = [Artist()] album = Album() track1 = Track(uri=u'uri', name=u'name', artists=artists, album=album, - track_no=1, date=date, length=100, bitrate=100, id=2) + track_no=1, date=date, length=100, bitrate=100) track2 = Track(uri=u'uri', name=u'name', artists=artists, album=album, - track_no=1, date=date, length=100, bitrate=100, id=2) + track_no=1, date=date, length=100, bitrate=100) self.assertEqual(track1, track2) self.assertEqual(hash(track1), hash(track2)) @@ -362,20 +350,14 @@ class TrackTest(unittest.TestCase): self.assertNotEqual(track1, track2) self.assertNotEqual(hash(track1), hash(track2)) - def test_ne_id(self): - track1 = Track(id=100) - track2 = Track(id=200) - self.assertNotEqual(track1, track2) - self.assertNotEqual(hash(track1), hash(track2)) - def test_ne(self): track1 = Track(uri=u'uri1', name=u'name1', artists=[Artist(name=u'name1')], album=Album(name=u'name1'), - track_no=1, date=dt.date.today(), length=100, bitrate=100, id=2) + track_no=1, date=dt.date.today(), length=100, bitrate=100) track2 = Track(uri=u'uri2', name=u'name2', artists=[Artist(name=u'name2')], album=Album(name=u'name2'), track_no=2, date=dt.date.today()-dt.timedelta(days=1), - length=200, bitrate=200, id=1) + length=200, bitrate=200) self.assertNotEqual(track1, track2) self.assertNotEqual(hash(track1), hash(track2)) diff --git a/tests/mpd/current_playlist_test.py b/tests/mpd/current_playlist_test.py index e8dd9748..245cabbb 100644 --- a/tests/mpd/current_playlist_test.py +++ b/tests/mpd/current_playlist_test.py @@ -55,7 +55,7 @@ class CurrentPlaylistHandlerTest(unittest.TestCase): self.assert_(u'OK' in result) def test_addid_with_songpos_out_of_bounds_should_ack(self): - needle = Track(uri='dummy://foo', id=137) + needle = Track(uri='dummy://foo') self.b.library._library = [Track(), Track(), needle, Track()] self.b.current_playlist.load( [Track(), Track(), Track(), Track(), Track()]) @@ -78,9 +78,10 @@ class CurrentPlaylistHandlerTest(unittest.TestCase): def test_delete_songpos(self): self.b.current_playlist.load( - [Track(id=1), Track(id=2), Track(id=3), Track(id=4), Track(id=5)]) + [Track(), Track(), Track(), Track(), Track()]) self.assertEqual(len(self.b.current_playlist.tracks), 5) - result = self.h.handle_request(u'delete "2"') + result = self.h.handle_request(u'delete "%d"' % + self.b.current_playlist.cp_tracks[2][0]) self.assertEqual(len(self.b.current_playlist.tracks), 4) self.assert_(u'OK' in result) @@ -94,7 +95,7 @@ class CurrentPlaylistHandlerTest(unittest.TestCase): def test_delete_open_range(self): self.b.current_playlist.load( - [Track(id=1), Track(id=2), Track(id=3), Track(id=4), Track(id=5)]) + [Track(), Track(), Track(), Track(), Track()]) self.assertEqual(len(self.b.current_playlist.tracks), 5) result = self.h.handle_request(u'delete "1:"') self.assertEqual(len(self.b.current_playlist.tracks), 1) @@ -102,7 +103,7 @@ class CurrentPlaylistHandlerTest(unittest.TestCase): def test_delete_closed_range(self): self.b.current_playlist.load( - [Track(id=1), Track(id=2), Track(id=3), Track(id=4), Track(id=5)]) + [Track(), Track(), Track(), Track(), Track()]) self.assertEqual(len(self.b.current_playlist.tracks), 5) result = self.h.handle_request(u'delete "1:3"') self.assertEqual(len(self.b.current_playlist.tracks), 3) diff --git a/tests/utils_test.py b/tests/utils_test.py index eacd314e..d5c98d86 100644 --- a/tests/utils_test.py +++ b/tests/utils_test.py @@ -146,7 +146,7 @@ expected_tracks = [] def generate_track(path, ident): uri = path_to_uri(data_folder(path)) track = Track(name='trackname', artists=expected_artists, track_no=1, - album=expected_albums[0], length=4000, uri=uri, id=ident) + album=expected_albums[0], length=4000, uri=uri) expected_tracks.append(track) generate_track('song1.mp3', 6) @@ -170,7 +170,7 @@ class MPDTagCacheToTracksTest(unittest.TestCase): data_folder('')) uri = path_to_uri(data_folder('song1.mp3')) track = Track(name='trackname', artists=expected_artists, track_no=1, - album=expected_albums[0], length=4000, uri=uri, id=0) + album=expected_albums[0], length=4000, uri=uri) self.assertEqual(set([track]), tracks) def test_advanced_cache(self): @@ -189,4 +189,4 @@ class MPDTagCacheToTracksTest(unittest.TestCase): tracks = parse_mpd_tag_cache(data_folder('blank_tag_cache'), data_folder('')) uri = path_to_uri(data_folder('song1.mp3')) - self.assertEqual(set([Track(uri=uri, length=4000, id=0)]), tracks) + self.assertEqual(set([Track(uri=uri, length=4000)]), tracks)