From 45c0c9b3cbcf4689eb22555f97e53c362f94c4fe Mon Sep 17 00:00:00 2001 From: Bruno Cauet Date: Tue, 17 Mar 2015 18:43:55 +0100 Subject: [PATCH] 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. --- beets/dbcore/query.py | 12 +++++++++ beetsplug/smartplaylist.py | 50 ++++++++++++++++++++++++++------------ test/test_smartplaylist.py | 37 +++++++++++++++++----------- 3 files changed, 69 insertions(+), 30 deletions(-) diff --git a/beets/dbcore/query.py b/beets/dbcore/query.py index ba7125634..a51805bed 100644 --- a/beets/dbcore/query.py +++ b/beets/dbcore/query.py @@ -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 diff --git a/beetsplug/smartplaylist.py b/beetsplug/smartplaylist.py index e7048a1cf..80e1faa89 100644 --- a/beetsplug/smartplaylist.py +++ b/beetsplug/smartplaylist.py @@ -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 = {} diff --git a/test/test_smartplaylist.py b/test/test_smartplaylist.py index fc9bba59a..42bffc8a5 100644 --- a/test/test_smartplaylist.py +++ b/test/test_smartplaylist.py @@ -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')