new BytesQuery factors out MatchQuery's path logic

This commit is contained in:
Adrian Sampson 2014-01-13 16:17:30 -08:00
parent 7f3a8ac505
commit cbbb38c417
6 changed files with 59 additions and 31 deletions

View file

@ -6,7 +6,6 @@ import sqlite3
import contextlib import contextlib
import beets import beets
from beets import util
from beets.util.functemplate import Template from beets.util.functemplate import Template
@ -433,29 +432,17 @@ class FieldQuery(Query):
""" """
raise NotImplementedError() raise NotImplementedError()
@classmethod
def _raw_value_match(cls, pattern, value):
"""Determine whether the value matches the pattern. The value
may have any type.
"""
return cls.value_match(pattern, util.as_string(value))
def match(self, item): def match(self, item):
return self._raw_value_match(self.pattern, item.get(self.field)) return self.value_match(self.pattern, item.get(self.field))
class MatchQuery(FieldQuery): class MatchQuery(FieldQuery):
"""A query that looks for exact matches in an item field.""" """A query that looks for exact matches in an item field."""
def col_clause(self): def col_clause(self):
pattern = self.pattern return self.field + " = ?", [self.pattern]
if self.field == 'path':
pattern = buffer(util.bytestring_path(pattern))
return self.field + " = ?", [pattern]
# We override the "raw" version here as a special case because we
# want to compare objects before conversion.
@classmethod @classmethod
def _raw_value_match(cls, pattern, value): def value_match(cls, pattern, value):
return pattern == value return pattern == value

View file

@ -24,6 +24,7 @@ from collections import defaultdict
from beets import autotag from beets import autotag
from beets import library from beets import library
from beets import dbcore
from beets import plugins from beets import plugins
from beets import util from beets import util
from beets import config from beets import config
@ -65,7 +66,7 @@ def _duplicate_check(lib, task):
found_albums = [] found_albums = []
cur_paths = set(i.path for i in task.items if i) cur_paths = set(i.path for i in task.items if i)
for album_cand in lib.albums(library.MatchQuery('albumartist', artist)): for album_cand in lib.albums(dbcore.MatchQuery('albumartist', artist)):
if album_cand.album == album: if album_cand.album == album:
# Check whether the album is identical in contents, in which # Check whether the album is identical in contents, in which
# case it is not a duplicate (will be replaced). # case it is not a duplicate (will be replaced).
@ -84,8 +85,8 @@ def _item_duplicate_check(lib, task):
found_items = [] found_items = []
query = library.AndQuery(( query = library.AndQuery((
library.MatchQuery('artist', artist), dbcore.MatchQuery('artist', artist),
library.MatchQuery('title', title), dbcore.MatchQuery('title', title),
)) ))
for other_item in lib.items(query): for other_item in lib.items(query):
# Existing items not considered duplicates. # Existing items not considered duplicates.
@ -751,7 +752,7 @@ def apply_choices(session):
task.replaced_items = defaultdict(list) task.replaced_items = defaultdict(list)
for item in items: for item in items:
dup_items = session.lib.items( dup_items = session.lib.items(
library.MatchQuery('path', item.path) library.BytesQuery('path', item.path)
) )
for dup_item in dup_items: for dup_item in dup_items:
task.replaced_items[item].append(dup_item) task.replaced_items[item].append(dup_item)

View file

@ -696,7 +696,26 @@ class Album(LibModel):
# Query abstraction hierarchy. # Query abstraction hierarchy.
class SubstringQuery(dbcore.FieldQuery): class StringFieldQuery(dbcore.FieldQuery):
"""A FieldQuery that converts values to strings before matching
them.
"""
@classmethod
def value_match(cls, pattern, value):
"""Determine whether the value matches the pattern. The value
may have any type.
"""
return cls.string_match(pattern, util.as_string(value))
@classmethod
def string_match(cls, pattern, value):
"""Determine whether the value matches the pattern. Both
arguments are strings. Subclasses implement this method.
"""
raise NotImplementedError()
class SubstringQuery(StringFieldQuery):
"""A query that matches a substring in a specific item field.""" """A query that matches a substring in a specific item field."""
def col_clause(self): def col_clause(self):
search = '%' + (self.pattern.replace('\\','\\\\').replace('%','\\%') search = '%' + (self.pattern.replace('\\','\\\\').replace('%','\\%')
@ -706,16 +725,16 @@ class SubstringQuery(dbcore.FieldQuery):
return clause, subvals return clause, subvals
@classmethod @classmethod
def value_match(cls, pattern, value): def string_match(cls, pattern, value):
return pattern.lower() in value.lower() return pattern.lower() in value.lower()
class RegexpQuery(dbcore.FieldQuery): class RegexpQuery(StringFieldQuery):
"""A query that matches a regular expression in a specific item """A query that matches a regular expression in a specific item
field. field.
""" """
@classmethod @classmethod
def value_match(cls, pattern, value): def string_match(cls, pattern, value):
try: try:
res = re.search(pattern, value) res = re.search(pattern, value)
except re.error: except re.error:
@ -735,6 +754,27 @@ class BooleanQuery(dbcore.MatchQuery):
self.pattern = int(self.pattern) self.pattern = int(self.pattern)
class BytesQuery(dbcore.MatchQuery):
"""Match a raw bytes field (i.e., a path). This is a necessary hack
to distinguish between the common case, matching Unicode strings,
and the special case in which we match bytes.
"""
def __init__(self, field, pattern):
super(BytesQuery, self).__init__(field, pattern)
# Use a buffer representation of the pattern for SQLite
# matching. This instructs SQLite to treat the blob as binary
# rather than encoded Unicode.
if isinstance(self.pattern, bytes):
self.buf_pattern = buffer(self.pattern)
elif isinstance(self.battern, buffer):
self.buf_pattern = self.pattern
self.pattern = bytes(self.pattern)
def col_clause(self):
return self.field + " = ?", [self.buf_pattern]
class NumericQuery(dbcore.FieldQuery): class NumericQuery(dbcore.FieldQuery):
"""Matches numeric fields. A syntax using Ruby-style range ellipses """Matches numeric fields. A syntax using Ruby-style range ellipses
(``..``) lets users specify one- or two-sided ranges. For example, (``..``) lets users specify one- or two-sided ranges. For example,

View file

@ -1017,7 +1017,7 @@ class Server(BaseServer):
def cmd_find(self, conn, *kv): def cmd_find(self, conn, *kv):
"""Perform an exact match for items.""" """Perform an exact match for items."""
query = self._metadata_query(beets.library.MatchQuery, query = self._metadata_query(beets.dbcore.MatchQuery,
None, None,
kv) kv)
for item in self.lib.items(query): for item in self.lib.items(query):
@ -1028,7 +1028,7 @@ class Server(BaseServer):
filtered by matching match_tag to match_term. filtered by matching match_tag to match_term.
""" """
show_tag_canon, show_key = self._tagtype_lookup(show_tag) show_tag_canon, show_key = self._tagtype_lookup(show_tag)
query = self._metadata_query(beets.library.MatchQuery, None, kv) query = self._metadata_query(beets.dbcore.MatchQuery, None, kv)
clause, subvals = query.clause() clause, subvals = query.clause()
statement = 'SELECT DISTINCT ' + show_key + \ statement = 'SELECT DISTINCT ' + show_key + \
@ -1047,7 +1047,7 @@ class Server(BaseServer):
_, key = self._tagtype_lookup(tag) _, key = self._tagtype_lookup(tag)
songs = 0 songs = 0
playtime = 0.0 playtime = 0.0
for item in self.lib.items(beets.library.MatchQuery(key, value)): for item in self.lib.items(beets.dbcore.MatchQuery(key, value)):
songs += 1 songs += 1
playtime += item.length playtime += item.length
yield u'songs: ' + unicode(songs) yield u'songs: ' + unicode(songs)

View file

@ -16,14 +16,14 @@
""" """
from beets.plugins import BeetsPlugin from beets.plugins import BeetsPlugin
from beets.library import FieldQuery from beets.library import StringFieldQuery
import beets import beets
import difflib import difflib
class FuzzyQuery(FieldQuery): class FuzzyQuery(StringFieldQuery):
@classmethod @classmethod
def value_match(self, pattern, val): def string_match(self, pattern, val):
# smartcase # smartcase
if pattern.islower(): if pattern.islower():
val = val.lower() val = val.lower()

View file

@ -165,7 +165,7 @@ class MPDStats(object):
def get_item(self, path): def get_item(self, path):
"""Return the beets item related to path. """Return the beets item related to path.
""" """
query = library.MatchQuery('path', path) query = library.BytesQuery('path', path)
item = self.lib.items(query).get() item = self.lib.items(query).get()
if item: if item:
return item return item