diff --git a/beets/library.py b/beets/library.py index 5c702c053..1de1bba56 100644 --- a/beets/library.py +++ b/beets/library.py @@ -39,6 +39,10 @@ log = logging.getLogger('beets') class PathQuery(dbcore.FieldQuery): """A query that matches all items under a given path.""" + + escape_re = re.compile(r'[\\_%]') + escape_char = '\\' + def __init__(self, field, pattern, fast=True): super(PathQuery, self).__init__(field, pattern, fast) @@ -52,10 +56,12 @@ class PathQuery(dbcore.FieldQuery): item.path.startswith(self.dir_path) def clause(self): - dir_pat = buffer(self.dir_path + '%') + escape = lambda m: self.escape_char + m.group(0) + dir_pattern = self.escape_re.sub(escape, self.dir_path) + dir_pattern = buffer(dir_pattern + '%') file_blob = buffer(self.file_path) - return '({0} = ?) || ({0} LIKE ?)'.format(self.field), \ - (file_blob, dir_pat) + return '({0} = ?) || ({0} LIKE ? ESCAPE ?)'.format(self.field), \ + (file_blob, dir_pattern, self.escape_char) # Library-specific field types. diff --git a/test/test_query.py b/test/test_query.py index e71f7f484..f2ad3cb6e 100644 --- a/test/test_query.py +++ b/test/test_query.py @@ -332,7 +332,7 @@ class MatchTest(_common.TestCase): self.assertFalse(q.match(self.item)) -class PathQueryTest(_common.LibTestCase, AssertsMixin): +class PathQueryTest(_common.LibTestCase, TestHelper, AssertsMixin): def setUp(self): super(PathQueryTest, self).setUp() self.i.path = '/a/b/c.mp3' @@ -389,6 +389,24 @@ class PathQueryTest(_common.LibTestCase, AssertsMixin): results = self.lib.items(q) self.assert_matched(results, ['path item']) + def test_escape_underscore(self): + self.add_item(path='/a/_/title.mp3', title='with underscore') + q = 'path:/a/_' + results = self.lib.items(q) + self.assert_matched(results, ['with underscore']) + + def test_escape_percent(self): + self.add_item(path='/a/%/title.mp3', title='with percent') + q = 'path:/a/%' + results = self.lib.items(q) + self.assert_matched(results, ['with percent']) + + def test_escape_backslash(self): + self.add_item(path=r'/a/\x/title.mp3', title='with backslash') + q = r'path:/a/\\x' + results = self.lib.items(q) + self.assert_matched(results, ['with backslash']) + class IntQueryTest(unittest.TestCase, TestHelper):