implement get() querying for device libraries

This commit is contained in:
Adrian Sampson 2010-04-06 17:49:19 -07:00
parent cd9bb22270
commit bae5ca5d70
3 changed files with 58 additions and 23 deletions

View file

@ -33,19 +33,19 @@ FIELD_MAP = {
'tracks': 'tracktotal', 'tracks': 'tracktotal',
} }
def track_to_item(track):
data = {}
for dname, bname in FIELD_MAP.items():
data[bname] = track[dname]
data['length'] = float(track['tracklen']) / 1000
data['path'] = track.ipod_filename()
return Item(data)
class PodLibrary(BaseLibrary): class PodLibrary(BaseLibrary):
def __init__(self, path): def __init__(self, path):
self.db = gpod.Database(path) self.db = gpod.Database(path)
self.syncing = False self.syncing = False
# Browsing convenience.
def artists(self, query=None):
raise NotImplementedError
def albums(self, artist=None, query=None):
raise NotImplementedError
def items(self, artist=None, album=None, title=None, query=None):
raise NotImplementedError
@classmethod @classmethod
def by_name(cls, name): def by_name(cls, name):
return cls(os.path.join(os.path.expanduser('~'), '.gvfs', name)) return cls(os.path.join(os.path.expanduser('~'), '.gvfs', name))
@ -82,7 +82,11 @@ class PodLibrary(BaseLibrary):
self.db.copy_delayed_files() self.db.copy_delayed_files()
def get(self, query=None): def get(self, query=None):
raise NotImplementedError query = self._get_query(query)
for track in self.db:
item = track_to_item(track)
if query.match(item):
yield item
def save(self): def save(self):
self._stop_sync() self._stop_sync()

View file

@ -267,6 +267,12 @@ class Query(object):
""" """
raise NotImplementedError raise NotImplementedError
def match(self, item):
"""Check whether this query matches a given Item. Can be used to
perform queries on arbitrary sets of Items.
"""
raise NotImplementedError
def statement(self, columns='*'): def statement(self, columns='*'):
"""Returns (query, subvals) where clause is a sqlite SELECT statement """Returns (query, subvals) where clause is a sqlite SELECT statement
to enact this query and subvals is a list of values to substitute in to enact this query and subvals is a list of values to substitute in
@ -298,7 +304,6 @@ class FieldQuery(Query):
"""An abstract query that searches in a specific field for a """An abstract query that searches in a specific field for a
pattern. pattern.
""" """
def __init__(self, field, pattern): def __init__(self, field, pattern):
if field not in item_keys: if field not in item_keys:
raise InvalidFieldError(field + ' is not an item key') raise InvalidFieldError(field + ' is not an item key')
@ -307,13 +312,14 @@ class FieldQuery(Query):
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 clause(self): def clause(self):
return self.field + " = ?", [self.pattern] return self.field + " = ?", [self.pattern]
def match(self, item):
return self.pattern == getattr(item, self.field)
class SubstringQuery(FieldQuery): class SubstringQuery(FieldQuery):
"""A query that matches a substring in a specific item field.""" """A query that matches a substring in a specific item field."""
def clause(self): def clause(self):
search = '%' + (self.pattern.replace('\\','\\\\').replace('%','\\%') search = '%' + (self.pattern.replace('\\','\\\\').replace('%','\\%')
.replace('_','\\_')) + '%' .replace('_','\\_')) + '%'
@ -321,11 +327,13 @@ class SubstringQuery(FieldQuery):
subvals = [search] subvals = [search]
return clause, subvals return clause, subvals
def match(self, item):
return self.pattern.lower() in getattr(item, self.field).lower()
class CollectionQuery(Query): class CollectionQuery(Query):
"""An abstract query class that aggregates other queries. Can be indexed """An abstract query class that aggregates other queries. Can be indexed
like a list to access the sub-queries. like a list to access the sub-queries.
""" """
def __init__(self, subqueries = ()): def __init__(self, subqueries = ()):
self.subqueries = subqueries self.subqueries = subqueries
@ -404,8 +412,8 @@ class CollectionQuery(Query):
class AnySubstringQuery(CollectionQuery): class AnySubstringQuery(CollectionQuery):
"""A query that matches a substring in any metadata field. """ """A query that matches a substring in any metadata field. """
def __init__(self, pattern): def __init__(self, pattern):
self.pattern = pattern
subqueries = [] subqueries = []
for field in metadata_rw_keys: for field in metadata_rw_keys:
subqueries.append(SubstringQuery(field, pattern)) subqueries.append(SubstringQuery(field, pattern))
@ -414,6 +422,17 @@ class AnySubstringQuery(CollectionQuery):
def clause(self): def clause(self):
return self.clause_with_joiner('or') return self.clause_with_joiner('or')
def match(self, item):
for fld in metadata_rw_keys:
try:
val = getattr(item, fld)
except KeyError:
continue
if isinstance(val, basestring) and \
self.pattern.lower() in val.lower():
return True
return False
class MutableCollectionQuery(CollectionQuery): class MutableCollectionQuery(CollectionQuery):
"""A collection query whose subqueries may be modified after the query is """A collection query whose subqueries may be modified after the query is
initialized. initialized.
@ -426,11 +445,17 @@ class AndQuery(MutableCollectionQuery):
def clause(self): def clause(self):
return self.clause_with_joiner('and') return self.clause_with_joiner('and')
def match(self, item):
return all([q.match(item) for q in self.subqueries])
class TrueQuery(Query): class TrueQuery(Query):
"""A query that always matches.""" """A query that always matches."""
def clause(self): def clause(self):
return '1', () return '1', ()
def match(self, item):
return True
class ResultIterator(object): class ResultIterator(object):
"""An iterator into an item query result set.""" """An iterator into an item query result set."""

10
bts
View file

@ -108,6 +108,8 @@ class BeetsApp(cmdln.Cmdln):
parser = cmdln.Cmdln.get_optparser(self) parser = cmdln.Cmdln.get_optparser(self)
parser.add_option('-l', '--library', dest='libpath', parser.add_option('-l', '--library', dest='libpath',
help='the library database file to use') help='the library database file to use')
parser.add_option('-d', '--device', dest='device',
help="the name of the device library to use")
return parser return parser
def postoptparse(self): def postoptparse(self):
@ -119,10 +121,14 @@ class BeetsApp(cmdln.Cmdln):
self.config.add_section(sec) self.config.add_section(sec)
# Open library file. # Open library file.
libpath = self.options.libpath or self.config.get('beets', 'library') if self.options.device:
from beets.device import PodLibrary
self.lib = PodLibrary.by_name(self.options.device)
else:
libpath = self.options.libpath or \
self.config.get('beets', 'library')
directory = self.config.get('beets', 'directory') directory = self.config.get('beets', 'directory')
path_format = self.config.get('beets', 'path_format') path_format = self.config.get('beets', 'path_format')
self.lib = Library(os.path.expanduser(libpath), self.lib = Library(os.path.expanduser(libpath),
directory, directory,
path_format) path_format)