mirror of
https://github.com/beetbox/beets.git
synced 2025-12-08 09:34:23 +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 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
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue