diff --git a/docs/changelog.rst b/docs/changelog.rst index df7de99a..e6082f90 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -8,6 +8,13 @@ This changelog is used to track all major changes to Mopidy. v0.19.0 (unreleased) ==================== +**Models** + +- The type of :attr:`mopidy.models.Playlist.last_modified` has been redefined + from a :class:`datetime.datetime` instance to the number of milliseconds + since Unix epoch as an integer. This makes serialization of the time stamp + simpler. + **MPD** - Minor refactor of context such that it stores password instead of config. diff --git a/mopidy/models.py b/mopidy/models.py index e1a1270f..42313922 100644 --- a/mopidy/models.py +++ b/mopidy/models.py @@ -418,8 +418,9 @@ class Playlist(ImmutableObject): :type name: string :param tracks: playlist's tracks :type tracks: list of :class:`Track` elements - :param last_modified: playlist's modification time in UTC - :type last_modified: :class:`datetime.datetime` + :param last_modified: + playlist's modification time in milliseconds since Unix epoch + :type last_modified: int """ #: The playlist URI. Read-only. @@ -431,9 +432,10 @@ class Playlist(ImmutableObject): #: The playlist's tracks. Read-only. tracks = tuple() - #: The playlist modification time in UTC. Read-only. + #: The playlist modification time in milliseconds since Unix epoch. + #: Read-only. #: - #: :class:`datetime.datetime`, or :class:`None` if unknown. + #: Integer, or :class:`None` if unknown. last_modified = None def __init__(self, *args, **kwargs): diff --git a/mopidy/mpd/protocol/stored_playlists.py b/mopidy/mpd/protocol/stored_playlists.py index 706e8df2..f4d48ff0 100644 --- a/mopidy/mpd/protocol/stored_playlists.py +++ b/mopidy/mpd/protocol/stored_playlists.py @@ -1,4 +1,4 @@ -from __future__ import unicode_literals +from __future__ import division, unicode_literals import datetime @@ -78,16 +78,27 @@ def listplaylists(context): continue name = context.lookup_playlist_name_from_uri(playlist.uri) result.append(('playlist', name)) - last_modified = ( - playlist.last_modified or datetime.datetime.utcnow()).isoformat() - # Remove microseconds - last_modified = last_modified.split('.')[0] - # Add time zone information - last_modified = last_modified + 'Z' - result.append(('Last-Modified', last_modified)) + result.append(('Last-Modified', _get_last_modified(playlist))) return result +# TODO: move to translators? +def _get_last_modified(playlist): + """Formats last modified timestamp of a playlist for MPD. + + Time in UTC with second precision, formatted in the ISO 8601 format, with + the "Z" time zone marker for UTC. For example, "1970-01-01T00:00:00Z". + """ + if playlist.last_modified is None: + # If unknown, assume the playlist is modified + dt = datetime.datetime.utcnow() + else: + dt = datetime.datetime.utcfromtimestamp( + playlist.last_modified / 1000.0) + dt = dt.replace(microsecond=0) + return '%sZ' % dt.isoformat() + + @protocol.commands.add('load', playlist_slice=protocol.RANGE) def load(context, name, playlist_slice=slice(0, None)): """ diff --git a/tests/mpd/protocol/test_music_db.py b/tests/mpd/protocol/test_music_db.py index d60a485f..6ea18caf 100644 --- a/tests/mpd/protocol/test_music_db.py +++ b/tests/mpd/protocol/test_music_db.py @@ -1,6 +1,5 @@ from __future__ import unicode_literals -import datetime import unittest from mopidy.mpd.protocol import music_db @@ -237,7 +236,7 @@ class MusicDatabaseHandlerTest(protocol.BaseTestCase): self.assertEqual(response1, response2) def test_lsinfo_without_path_returns_same_as_for_root(self): - last_modified = datetime.datetime(2001, 3, 17, 13, 41, 17, 12345) + last_modified = 1390942873222 self.backend.playlists.playlists = [ Playlist(name='a', uri='dummy:/a', last_modified=last_modified)] @@ -246,7 +245,7 @@ class MusicDatabaseHandlerTest(protocol.BaseTestCase): self.assertEqual(response1, response2) def test_lsinfo_with_empty_path_returns_same_as_for_root(self): - last_modified = datetime.datetime(2001, 3, 17, 13, 41, 17, 12345) + last_modified = 1390942873222 self.backend.playlists.playlists = [ Playlist(name='a', uri='dummy:/a', last_modified=last_modified)] @@ -255,14 +254,14 @@ class MusicDatabaseHandlerTest(protocol.BaseTestCase): self.assertEqual(response1, response2) def test_lsinfo_for_root_includes_playlists(self): - last_modified = datetime.datetime(2001, 3, 17, 13, 41, 17, 12345) + last_modified = 1390942873222 self.backend.playlists.playlists = [ Playlist(name='a', uri='dummy:/a', last_modified=last_modified)] self.sendRequest('lsinfo "/"') self.assertInResponse('playlist: a') - # Date without microseconds and with time zone information - self.assertInResponse('Last-Modified: 2001-03-17T13:41:17Z') + # Date without milliseconds and with time zone information + self.assertInResponse('Last-Modified: 2014-01-28T21:01:13Z') self.assertInResponse('OK') def test_lsinfo_for_root_includes_dirs_for_each_lib_with_content(self): diff --git a/tests/mpd/protocol/test_stored_playlists.py b/tests/mpd/protocol/test_stored_playlists.py index 636c5c2c..857ed03e 100644 --- a/tests/mpd/protocol/test_stored_playlists.py +++ b/tests/mpd/protocol/test_stored_playlists.py @@ -1,7 +1,5 @@ from __future__ import unicode_literals -import datetime - from mopidy.models import Track, Playlist from tests.mpd import protocol @@ -78,14 +76,14 @@ class PlaylistsHandlerTest(protocol.BaseTestCase): self.assertInResponse('OK') def test_listplaylists(self): - last_modified = datetime.datetime(2001, 3, 17, 13, 41, 17, 12345) + last_modified = 1390942873222 self.backend.playlists.playlists = [ Playlist(name='a', uri='dummy:a', last_modified=last_modified)] self.sendRequest('listplaylists') self.assertInResponse('playlist: a') - # Date without microseconds and with time zone information - self.assertInResponse('Last-Modified: 2001-03-17T13:41:17Z') + # Date without milliseconds and with time zone information + self.assertInResponse('Last-Modified: 2014-01-28T21:01:13Z') self.assertInResponse('OK') def test_listplaylists_duplicate(self): @@ -99,7 +97,7 @@ class PlaylistsHandlerTest(protocol.BaseTestCase): self.assertInResponse('OK') def test_listplaylists_ignores_playlists_without_name(self): - last_modified = datetime.datetime(2001, 3, 17, 13, 41, 17, 12345) + last_modified = 1390942873222 self.backend.playlists.playlists = [ Playlist(name='', uri='dummy:', last_modified=last_modified)] diff --git a/tests/test_models.py b/tests/test_models.py index 9a4f97b7..13ab637f 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -1,6 +1,5 @@ from __future__ import unicode_literals -import datetime import json import unittest @@ -842,7 +841,7 @@ class PlaylistTest(unittest.TestCase): self.assertEqual(playlist.length, 3) def test_last_modified(self): - last_modified = datetime.datetime.utcnow() + last_modified = 1390942873000 playlist = Playlist(last_modified=last_modified) self.assertEqual(playlist.last_modified, last_modified) self.assertRaises( @@ -850,7 +849,7 @@ class PlaylistTest(unittest.TestCase): def test_with_new_uri(self): tracks = [Track()] - last_modified = datetime.datetime.utcnow() + last_modified = 1390942873000 playlist = Playlist( uri='an uri', name='a name', tracks=tracks, last_modified=last_modified) @@ -862,7 +861,7 @@ class PlaylistTest(unittest.TestCase): def test_with_new_name(self): tracks = [Track()] - last_modified = datetime.datetime.utcnow() + last_modified = 1390942873000 playlist = Playlist( uri='an uri', name='a name', tracks=tracks, last_modified=last_modified) @@ -874,7 +873,7 @@ class PlaylistTest(unittest.TestCase): def test_with_new_tracks(self): tracks = [Track()] - last_modified = datetime.datetime.utcnow() + last_modified = 1390942873000 playlist = Playlist( uri='an uri', name='a name', tracks=tracks, last_modified=last_modified) @@ -887,8 +886,8 @@ class PlaylistTest(unittest.TestCase): def test_with_new_last_modified(self): tracks = [Track()] - last_modified = datetime.datetime.utcnow() - new_last_modified = last_modified + datetime.timedelta(1) + last_modified = 1390942873000 + new_last_modified = last_modified + 1000 playlist = Playlist( uri='an uri', name='a name', tracks=tracks, last_modified=last_modified)