Merge pull request #1136 from adamcik/feature/tlid-in-core-tracklist-calls

Support / use TLID in core tracklist calls
This commit is contained in:
Stein Magnus Jodal 2015-04-21 08:32:37 +02:00
commit 0dfa3df39b
4 changed files with 170 additions and 33 deletions

View File

@ -200,19 +200,52 @@ class TracklistController(object):
# Methods
def index(self, tl_track):
def index(self, tl_track=None, tlid=None):
"""
The position of the given track in the tracklist.
If neither *tl_track* or *tlid* is given we return the index of
the currently playing track.
:param tl_track: the track to find the index of
:type tl_track: :class:`mopidy.models.TlTrack` or :class:`None`
:param tlid: TLID of the track to find the index of
:type tlid: :class:`int` or :class:`None`
:rtype: :class:`int` or :class:`None`
.. versionchanged:: 1.1
Added the *tlid* parameter
"""
tl_track is None or validation.check_instance(tl_track, TlTrack)
try:
return self._tl_tracks.index(tl_track)
except ValueError:
return None
tlid is None or validation.check_integer(tlid, min=0)
if tl_track is None and tlid is None:
tl_track = self.core.playback.get_current_tl_track()
if tl_track is not None:
try:
return self._tl_tracks.index(tl_track)
except ValueError:
pass
elif tlid is not None:
for i, tl_track in enumerate(self._tl_tracks):
if tl_track.tlid == tlid:
return i
return None
def get_eot_tlid(self):
"""
The TLID of the track that will be played after the given track.
Not necessarily the same TLID as returned by :meth:`get_next_tlid`.
:rtype: :class:`int` or :class:`None`
.. versionadded:: 1.1
"""
current_tl_track = self.core.playback.get_current_tl_track()
return getattr(self.eot_track(current_tl_track), 'tlid', None)
def eot_track(self, tl_track):
"""
@ -224,6 +257,7 @@ class TracklistController(object):
:type tl_track: :class:`mopidy.models.TlTrack` or :class:`None`
:rtype: :class:`mopidy.models.TlTrack` or :class:`None`
"""
deprecation.warn('core.tracklist.eot_track', pending=True)
tl_track is None or validation.check_instance(tl_track, TlTrack)
if self.get_single() and self.get_repeat():
return tl_track
@ -235,6 +269,23 @@ class TracklistController(object):
# shared.
return self.next_track(tl_track)
def get_next_tlid(self):
"""
The tlid of the track that will be played if calling
:meth:`mopidy.core.PlaybackController.next()`.
For normal playback this is the next track in the tracklist. If repeat
is enabled the next track can loop around the tracklist. When random is
enabled this should be a random track, all tracks should be played once
before the tracklist repeats.
:rtype: :class:`int` or :class:`None`
.. versionadded:: 1.1
"""
current_tl_track = self.core.playback.get_current_tl_track()
return getattr(self.next_track(current_tl_track), 'tlid', None)
def next_track(self, tl_track):
"""
The track that will be played if calling
@ -249,35 +300,51 @@ class TracklistController(object):
:type tl_track: :class:`mopidy.models.TlTrack` or :class:`None`
:rtype: :class:`mopidy.models.TlTrack` or :class:`None`
"""
deprecation.warn('core.tracklist.next_track', pending=True)
tl_track is None or validation.check_instance(tl_track, TlTrack)
if not self.get_tl_tracks():
if not self._tl_tracks:
return None
if self.get_random() and not self._shuffled:
if self.get_repeat() or not tl_track:
logger.debug('Shuffling tracks')
self._shuffled = self.get_tl_tracks()
self._shuffled = self._tl_tracks[:]
random.shuffle(self._shuffled)
if self.get_random():
try:
if self._shuffled:
return self._shuffled[0]
except IndexError:
return None
return None
if tl_track is None:
return self.get_tl_tracks()[0]
next_index = 0
else:
next_index = self.index(tl_track) + 1
next_index = self.index(tl_track) + 1
if self.get_repeat():
next_index %= len(self.get_tl_tracks())
try:
return self.get_tl_tracks()[next_index]
except IndexError:
next_index %= len(self._tl_tracks)
elif next_index >= len(self._tl_tracks):
return None
return self._tl_tracks[next_index]
def get_previous_tlid(self):
"""
Returns the TLID of the track that will be played if calling
:meth:`mopidy.core.PlaybackController.previous()`.
For normal playback this is the previous track in the tracklist. If
random and/or consume is enabled it should return the current track
instead.
:rtype: :class:`int` or :class:`None`
.. versionadded:: 1.1
"""
current_tl_track = self.core.playback.get_current_tl_track()
return getattr(self.previous_track(current_tl_track), 'tlid', None)
def previous_track(self, tl_track):
"""
Returns the track that will be played if calling
@ -291,6 +358,7 @@ class TracklistController(object):
:type tl_track: :class:`mopidy.models.TlTrack` or :class:`None`
:rtype: :class:`mopidy.models.TlTrack` or :class:`None`
"""
deprecation.warn('core.tracklist.previous_track', pending=True)
tl_track is None or validation.check_instance(tl_track, TlTrack)
if self.get_repeat() or self.get_consume() or self.get_random():
@ -301,7 +369,9 @@ class TracklistController(object):
if position in (None, 0):
return None
return self.get_tl_tracks()[position - 1]
# Since we know we are not at zero we have to be somewhere in the range
# 1 - len(tracks) Thus 'position - 1' will always be within the list.
return self._tl_tracks[position - 1]
def add(self, tracks=None, at_position=None, uri=None, uris=None):
"""
@ -554,7 +624,7 @@ class TracklistController(object):
def _trigger_tracklist_changed(self):
if self.get_random():
self._shuffled = self.get_tl_tracks()
self._shuffled = self._tl_tracks[:]
random.shuffle(self._shuffled)
else:
self._shuffled = []

View File

@ -45,13 +45,27 @@ _MESSAGES = {
'core.tracklist.remove:kwargs_criteria':
'tracklist.remove() with "kwargs" as criteria is deprecated',
'core.tracklist.eot_track':
'tracklist.eot_track() is pending deprecation, use '
'tracklist.get_eot_tlid()',
'core.tracklist.next_track':
'tracklist.next_track() is pending deprecation, use '
'tracklist.get_next_tlid()',
'core.tracklist.previous_track':
'tracklist.previous_track() is pending deprecation, use '
'tracklist.get_previous_tlid()',
'models.immutable.copy':
'ImmutableObject.copy() is deprecated, use ImmutableObject.replace()',
}
def warn(msg_id):
warnings.warn(_MESSAGES.get(msg_id, msg_id), DeprecationWarning)
def warn(msg_id, pending=False):
if pending:
category = PendingDeprecationWarning
else:
category = DeprecationWarning
warnings.warn(_MESSAGES.get(msg_id, msg_id), category)
@contextlib.contextmanager

View File

@ -5,7 +5,7 @@ import unittest
import mock
from mopidy import backend, core
from mopidy.models import Track
from mopidy.models import TlTrack, Track
from mopidy.utils import deprecation
@ -102,3 +102,66 @@ class TracklistTest(unittest.TestCase):
self.core.tracklist.filter({'uri': 'a'})
# TODO Extract tracklist tests from the local backend tests
class TracklistIndexTest(unittest.TestCase):
def setUp(self): # noqa: N802
self.tracks = [
Track(uri='dummy1:a', name='foo'),
Track(uri='dummy1:b', name='foo'),
Track(uri='dummy1:c', name='bar'),
]
def lookup(uris):
return {u: [t for t in self.tracks if t.uri == u] for u in uris}
self.core = core.Core(mixer=None, backends=[])
self.core.library = mock.Mock(spec=core.LibraryController)
self.core.library.lookup.side_effect = lookup
self.core.playback = mock.Mock(spec=core.PlaybackController)
self.tl_tracks = self.core.tracklist.add(uris=[
t.uri for t in self.tracks])
def test_index_returns_index_of_track(self):
self.assertEqual(0, self.core.tracklist.index(self.tl_tracks[0]))
self.assertEqual(1, self.core.tracklist.index(self.tl_tracks[1]))
self.assertEqual(2, self.core.tracklist.index(self.tl_tracks[2]))
def test_index_returns_none_if_item_not_found(self):
tl_track = TlTrack(0, Track())
self.assertEqual(self.core.tracklist.index(tl_track), None)
def test_index_returns_none_if_called_with_none(self):
self.assertEqual(self.core.tracklist.index(None), None)
def test_index_errors_out_for_invalid_tltrack(self):
with self.assertRaises(ValueError):
self.core.tracklist.index('abc')
def test_index_return_index_when_called_with_tlids(self):
tl_tracks = self.tl_tracks
self.assertEqual(0, self.core.tracklist.index(tlid=tl_tracks[0].tlid))
self.assertEqual(1, self.core.tracklist.index(tlid=tl_tracks[1].tlid))
self.assertEqual(2, self.core.tracklist.index(tlid=tl_tracks[2].tlid))
def test_index_returns_none_if_tlid_not_found(self):
self.assertEqual(self.core.tracklist.index(tlid=123), None)
def test_index_returns_none_if_called_with_tlid_none(self):
self.assertEqual(self.core.tracklist.index(tlid=None), None)
def test_index_errors_out_for_invalid_tlid(self):
with self.assertRaises(ValueError):
self.core.tracklist.index(tlid=-1)
def test_index_without_args_returns_current_tl_track_index(self):
self.core.playback.get_current_tl_track.side_effect = [
None, self.tl_tracks[0], self.tl_tracks[1], self.tl_tracks[2]]
self.assertEqual(None, self.core.tracklist.index())
self.assertEqual(0, self.core.tracklist.index())
self.assertEqual(1, self.core.tracklist.index())
self.assertEqual(2, self.core.tracklist.index())

View File

@ -8,7 +8,7 @@ import pykka
from mopidy import core
from mopidy.core import PlaybackState
from mopidy.local import actor
from mopidy.models import Playlist, TlTrack, Track
from mopidy.models import Playlist, Track
from mopidy.utils import deprecation
from tests import dummy_audio, path_to_data_dir
@ -176,16 +176,6 @@ class LocalTracklistProviderTest(unittest.TestCase):
tl_tracks = self.controller.add(self.controller.tracks[1:2])
self.assertEqual(tl_tracks[0].track, self.controller.tracks[1])
def test_index_returns_index_of_track(self):
tl_tracks = self.controller.add(self.tracks)
self.assertEqual(0, self.controller.index(tl_tracks[0]))
self.assertEqual(1, self.controller.index(tl_tracks[1]))
self.assertEqual(2, self.controller.index(tl_tracks[2]))
def test_index_returns_none_if_item_not_found(self):
tl_track = TlTrack(0, Track())
self.assertEqual(self.controller.index(tl_track), None)
@populate_tracklist
def test_move_single(self):
self.controller.move(0, 0, 2)