Deal with sorting

Try to follow any sort found & manage absence of sort. When there are
multiple sort directives given, concatenate them.

Tests not extended yet.
This commit is contained in:
Bruno Cauet 2015-03-17 18:43:55 +01:00
parent 65b52b9c48
commit 45c0c9b3cb
3 changed files with 69 additions and 30 deletions

View file

@ -730,3 +730,15 @@ class NullSort(Sort):
"""No sorting. Leave results unsorted."""
def sort(items):
return items
def __nonzero__(self):
return self.__bool__()
def __bool__(self):
return False
def __eq__(self, other):
return type(self) == type(other) or other is None
def __hash__(self):
return 0

View file

@ -23,6 +23,7 @@ from beets import ui
from beets.util import mkdirall, normpath, syspath
from beets.library import Item, Album, parse_query_string
from beets.dbcore import OrQuery
from beets.dbcore.query import MultipleSort
import os
@ -77,9 +78,16 @@ class SmartPlaylistPlugin(BeetsPlugin):
"""
Instanciate queries for the playlists.
Each playlist has 2 queries: one or items one for albums. We must also
remember its name. _unmatched_playlists is a set of tuples
(name, q, album_q).
Each playlist has 2 queries: one or items one for albums, each with a
sort. We must also remember its name. _unmatched_playlists is a set of
tuples (name, (q, q_sort), (album_q, album_q_sort)).
sort may be any sort, or NullSort, or None. None and NullSort are
equivalent and both eval to False.
More precisely
- it will be NullSort when a playlist query ('query' or 'album_query')
is a single item or a list with 1 element
- it will be None when there are multiple items i a query
"""
self._unmatched_playlists = set()
self._matched_playlists = set()
@ -88,18 +96,28 @@ class SmartPlaylistPlugin(BeetsPlugin):
playlist_data = (playlist['name'],)
for key, Model in (('query', Item), ('album_query', Album)):
qs = playlist.get(key)
# FIXME sort mgmt
if qs is None:
query = None
sort = None
query_and_sort = None, None
elif isinstance(qs, basestring):
query, sort = parse_query_string(qs, Model)
query_and_sort = parse_query_string(qs, Model)
elif len(qs) == 1:
query_and_sort = parse_query_string(qs[0], Model)
else:
query = OrQuery([parse_query_string(q, Model)[0]
for q in qs])
sort = None
del sort # FIXME
playlist_data += (query,)
# multiple queries and sorts
queries, sorts = zip(*(parse_query_string(q, Model)
for q in qs))
query = OrQuery(queries)
sort = MultipleSort()
for s in sorts:
if s:
sort.add_sort(s)
if not sort.sorts:
sort = None
elif len(sort.sorts) == 1:
sort = sort.sorts[0]
query_and_sort = query, sort
playlist_data += (query_and_sort,)
self._unmatched_playlists.add(playlist_data)
@ -108,7 +126,7 @@ class SmartPlaylistPlugin(BeetsPlugin):
self.build_queries()
for playlist in self._unmatched_playlists:
n, q, a_q = playlist
n, (q, _), (a_q, _) = playlist
if a_q and isinstance(model, Album):
matches = a_q.match(model)
elif q and isinstance(model, Item):
@ -133,14 +151,14 @@ class SmartPlaylistPlugin(BeetsPlugin):
relative_to = normpath(relative_to)
for playlist in self._matched_playlists:
name, query, album_query = playlist
name, (query, q_sort), (album_query, a_q_sort) = playlist
self._log.debug(u"Creating playlist {0}", name)
items = []
if query:
items.extend(lib.items(query))
items.extend(lib.items(query, q_sort))
if album_query:
for album in lib.albums(album_query):
for album in lib.albums(album_query, a_q_sort):
items.extend(album.items())
m3us = {}

View file

@ -24,6 +24,7 @@ from mock import Mock, MagicMock
from beetsplug.smartplaylist import SmartPlaylistPlugin
from beets.library import Item, Album, parse_query_string
from beets.dbcore import OrQuery
from beets.dbcore.query import NullSort
from beets.util import syspath
from beets.ui import UserError
from beets import config
@ -54,15 +55,15 @@ class SmartPlaylistTest(unittest.TestCase):
])
spl.build_queries()
self.assertEqual(spl._matched_playlists, set())
foo_foo, _ = parse_query_string('FOO foo', Item)
bar_bar = OrQuery([parse_query_string('BAR bar1', Album)[0],
parse_query_string('BAR bar2', Album)[0]])
baz_baz, _ = parse_query_string('BAZ baz', Item)
baz_baz2, _ = parse_query_string('BAZ baz', Album)
foo_foo = parse_query_string('FOO foo', Item)
baz_baz = parse_query_string('BAZ baz', Item)
baz_baz2 = parse_query_string('BAZ baz', Album)
bar_bar = OrQuery((parse_query_string('BAR bar1', Album)[0],
parse_query_string('BAR bar2', Album)[0]))
self.assertEqual(spl._unmatched_playlists, set([
('foo', foo_foo, None),
('bar', None, bar_bar),
('baz', baz_baz, baz_baz2)
('foo', foo_foo, (None, None)),
('baz', baz_baz, baz_baz2),
('bar', (None, None), (bar_bar, None)),
]))
def test_db_changes(self):
@ -80,9 +81,9 @@ class SmartPlaylistTest(unittest.TestCase):
q2 = Mock()
q2.matches.side_effect = {i1: False, i2: True}.__getitem__
pl1 = ('1', q1, a_q1)
pl2 = ('2', None, a_q1)
pl3 = ('3', q2, None)
pl1 = '1', (q1, None), (a_q1, None)
pl2 = '2', (None, None), (a_q1, None)
pl3 = '3', (q2, None), (None, None)
spl._unmatched_playlists = set([pl1, pl2, pl3])
spl._matched_playlists = set()
@ -115,7 +116,7 @@ class SmartPlaylistTest(unittest.TestCase):
lib = Mock()
lib.items.return_value = [i]
lib.albums.return_value = []
pl = 'my_playlist.m3u', q, a_q
pl = 'my_playlist.m3u', (q, None), (a_q, None)
spl._matched_playlists = [pl]
dir = mkdtemp()
@ -127,8 +128,8 @@ class SmartPlaylistTest(unittest.TestCase):
rmtree(dir)
raise
lib.items.assert_called_once_with(q)
lib.albums.assert_called_once_with(a_q)
lib.items.assert_called_once_with(q, None)
lib.albums.assert_called_once_with(a_q, None)
m3u_filepath = path.join(dir, pl[0])
self.assertTrue(path.exists(m3u_filepath))
@ -177,3 +178,11 @@ class SmartPlaylistCLITest(unittest.TestCase, TestHelper):
for name in ('my_playlist.m3u', 'all.m3u'):
with open(path.join(self.temp_dir, name), 'r') as f:
self.assertEqual(f.read(), self.item.path + b"\n")
def suite():
return unittest.TestLoader().loadTestsFromName(__name__)
if __name__ == b'__main__':
unittest.main(defaultTest='suite')