mirror of
https://github.com/beetbox/beets.git
synced 2025-12-06 16:42:42 +01:00
new BytesQuery factors out MatchQuery's path logic
This commit is contained in:
parent
7f3a8ac505
commit
cbbb38c417
6 changed files with 59 additions and 31 deletions
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue