diff --git a/beets/importer.py b/beets/importer.py index 17c15429d..2e44d5cdb 100644 --- a/beets/importer.py +++ b/beets/importer.py @@ -729,6 +729,10 @@ def plugin_stage(func): continue func(task) + # Stage may modify DB, so re-load cached item data. + for item in task.imported_items(): + config.lib.load(item) + def manipulate_files(): """A coroutine (pipeline stage) that performs necessary file manipulations *after* items have been added to the library. @@ -739,7 +743,7 @@ def manipulate_files(): if task.should_skip(): continue - # Move/copy files. + # Move/copy/write files. items = task.imported_items() task.old_paths = [item.path for item in items] # For deletion. for item in items: diff --git a/beets/library.py b/beets/library.py index 40265231e..ef97161a6 100644 --- a/beets/library.py +++ b/beets/library.py @@ -166,10 +166,7 @@ def _regexp(expr, val): """ if expr is None: return False - if val is None: - val = u'' - if not isinstance(val, basestring): - val = unicode(val) + val = util.as_string(val) try: res = re.search(expr, val) except re.error: @@ -436,7 +433,7 @@ class SubstringQuery(FieldQuery): return clause, subvals def match(self, item): - value = getattr(item, self.field) or '' + value = util.as_string(getattr(item, self.field)) return self.pattern.lower() in value.lower() class RegexpQuery(FieldQuery): @@ -451,7 +448,7 @@ class RegexpQuery(FieldQuery): return clause, subvals def match(self, item): - value = getattr(item, self.field) or '' + value = util.as_string(getattr(item, self.field)) return self.regexp.search(value) is not None class BooleanQuery(MatchQuery): diff --git a/beets/util/__init__.py b/beets/util/__init__.py index 380bfaf8e..6c29c7c0e 100644 --- a/beets/util/__init__.py +++ b/beets/util/__init__.py @@ -467,6 +467,15 @@ def str2bool(value): else: return False +def as_string(value): + """Convert a value to a Unicode object for matching with a query. + None becomes the empty string. + """ + if value is None: + return u'' + else: + return unicode(value) + def levenshtein(s1, s2): """A nice DP edit distance implementation from Wikibooks: http://en.wikibooks.org/wiki/Algorithm_implementation/Strings/ diff --git a/docs/changelog.rst b/docs/changelog.rst index 08ad9247d..03aab8744 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -12,6 +12,7 @@ Changelog * Fix album queries for ``artpath`` and other non-item fields. * Null values in the database can now be matched with the empty-string regular expression, ``^$``. +* Queries now correctly match non-string values in path format predicates. * :doc:`/plugins/fetchart`: Fix a bug where cover art filenames could lack a ``.jpg`` extension. * :doc:`/plugins/lyrics`: Fix an exception with non-ASCII lyrics. @@ -22,6 +23,8 @@ Changelog * Add the track mapping dictionary to the ``album_distance`` plugin function. * Fix an assertion failure when the MusicBrainz main database and search server disagree. +* Fix a bug that caused the :doc:`/plugins/lastgenre` and other plugins not to + modify files' tags even when they successfully change the database. .. _Tomahawk resolver: http://beets.radbox.org/blog/tomahawk-resolver.html diff --git a/test/test_query.py b/test/test_query.py index 5a4debefc..eae9abced 100644 --- a/test/test_query.py +++ b/test/test_query.py @@ -302,6 +302,34 @@ class MemoryGetTest(unittest.TestCase, AssertsMixin): self.assert_matched(results, u'caf\xe9') self.assert_done(results) +class MatchTest(unittest.TestCase): + def setUp(self): + self.item = _common.item() + + def test_regex_match_positive(self): + q = beets.library.RegexpQuery('album', '^the album$') + self.assertTrue(q.match(self.item)) + + def test_regex_match_negative(self): + q = beets.library.RegexpQuery('album', '^album$') + self.assertFalse(q.match(self.item)) + + def test_regex_match_non_string_value(self): + q = beets.library.RegexpQuery('disc', '^6$') + self.assertTrue(q.match(self.item)) + + def test_substring_match_positive(self): + q = beets.library.SubstringQuery('album', 'album') + self.assertTrue(q.match(self.item)) + + def test_substring_match_negative(self): + q = beets.library.SubstringQuery('album', 'ablum') + self.assertFalse(q.match(self.item)) + + def test_substring_match_non_string_value(self): + q = beets.library.SubstringQuery('disc', '6') + self.assertTrue(q.match(self.item)) + class PathQueryTest(unittest.TestCase, AssertsMixin): def setUp(self): self.lib = beets.library.Library(':memory:')