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 beets
from beets import util
from beets.util.functemplate import Template
@ -433,29 +432,17 @@ class FieldQuery(Query):
"""
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):
return self._raw_value_match(self.pattern, item.get(self.field))
return self.value_match(self.pattern, item.get(self.field))
class MatchQuery(FieldQuery):
"""A query that looks for exact matches in an item field."""
def col_clause(self):
pattern = self.pattern
if self.field == 'path':
pattern = buffer(util.bytestring_path(pattern))
return self.field + " = ?", [pattern]
return self.field + " = ?", [self.pattern]
# We override the "raw" version here as a special case because we
# want to compare objects before conversion.
@classmethod
def _raw_value_match(cls, pattern, value):
def value_match(cls, pattern, value):
return pattern == value

View file

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

View file

@ -696,7 +696,26 @@ class Album(LibModel):
# 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."""
def col_clause(self):
search = '%' + (self.pattern.replace('\\','\\\\').replace('%','\\%')
@ -706,16 +725,16 @@ class SubstringQuery(dbcore.FieldQuery):
return clause, subvals
@classmethod
def value_match(cls, pattern, value):
def string_match(cls, pattern, value):
return pattern.lower() in value.lower()
class RegexpQuery(dbcore.FieldQuery):
class RegexpQuery(StringFieldQuery):
"""A query that matches a regular expression in a specific item
field.
"""
@classmethod
def value_match(cls, pattern, value):
def string_match(cls, pattern, value):
try:
res = re.search(pattern, value)
except re.error:
@ -735,6 +754,27 @@ class BooleanQuery(dbcore.MatchQuery):
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):
"""Matches numeric fields. A syntax using Ruby-style range ellipses
(``..``) 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):
"""Perform an exact match for items."""
query = self._metadata_query(beets.library.MatchQuery,
query = self._metadata_query(beets.dbcore.MatchQuery,
None,
kv)
for item in self.lib.items(query):
@ -1028,7 +1028,7 @@ class Server(BaseServer):
filtered by matching match_tag to match_term.
"""
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()
statement = 'SELECT DISTINCT ' + show_key + \
@ -1047,7 +1047,7 @@ class Server(BaseServer):
_, key = self._tagtype_lookup(tag)
songs = 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
playtime += item.length
yield u'songs: ' + unicode(songs)

View file

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

View file

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