From bae5ca5d701addd9dea42c7824d808fb38b969a0 Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Tue, 6 Apr 2010 17:49:19 -0700 Subject: [PATCH] implement get() querying for device libraries --- beets/device.py | 26 +++++++++++++++----------- beets/library.py | 35 ++++++++++++++++++++++++++++++----- bts | 20 +++++++++++++------- 3 files changed, 58 insertions(+), 23 deletions(-) diff --git a/beets/device.py b/beets/device.py index 59824bd39..e672dce81 100644 --- a/beets/device.py +++ b/beets/device.py @@ -33,19 +33,19 @@ FIELD_MAP = { '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): def __init__(self, path): self.db = gpod.Database(path) 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 def by_name(cls, name): return cls(os.path.join(os.path.expanduser('~'), '.gvfs', name)) @@ -65,7 +65,7 @@ class PodLibrary(BaseLibrary): if hasattr(gpod, 'itdb_stop_sync'): gpod.itdb_stop_sync(self.db._itdb) self.syncing = False - + def add(self, item): self._start_sync() track = self.db.new_Track() @@ -82,7 +82,11 @@ class PodLibrary(BaseLibrary): self.db.copy_delayed_files() 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): self._stop_sync() diff --git a/beets/library.py b/beets/library.py index 87f8f9a8d..b60cfcfd3 100644 --- a/beets/library.py +++ b/beets/library.py @@ -267,6 +267,12 @@ class Query(object): """ 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='*'): """Returns (query, subvals) where clause is a sqlite SELECT statement 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 pattern. """ - def __init__(self, field, pattern): if field not in item_keys: raise InvalidFieldError(field + ' is not an item key') @@ -307,13 +312,14 @@ class FieldQuery(Query): class MatchQuery(FieldQuery): """A query that looks for exact matches in an item field.""" - def clause(self): return self.field + " = ?", [self.pattern] + def match(self, item): + return self.pattern == getattr(item, self.field) + class SubstringQuery(FieldQuery): """A query that matches a substring in a specific item field.""" - def clause(self): search = '%' + (self.pattern.replace('\\','\\\\').replace('%','\\%') .replace('_','\\_')) + '%' @@ -321,11 +327,13 @@ class SubstringQuery(FieldQuery): subvals = [search] return clause, subvals + def match(self, item): + return self.pattern.lower() in getattr(item, self.field).lower() + class CollectionQuery(Query): """An abstract query class that aggregates other queries. Can be indexed like a list to access the sub-queries. """ - def __init__(self, subqueries = ()): self.subqueries = subqueries @@ -404,8 +412,8 @@ class CollectionQuery(Query): class AnySubstringQuery(CollectionQuery): """A query that matches a substring in any metadata field. """ - def __init__(self, pattern): + self.pattern = pattern subqueries = [] for field in metadata_rw_keys: subqueries.append(SubstringQuery(field, pattern)) @@ -414,6 +422,17 @@ class AnySubstringQuery(CollectionQuery): def clause(self): 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): """A collection query whose subqueries may be modified after the query is initialized. @@ -426,11 +445,17 @@ class AndQuery(MutableCollectionQuery): def clause(self): return self.clause_with_joiner('and') + def match(self, item): + return all([q.match(item) for q in self.subqueries]) + class TrueQuery(Query): """A query that always matches.""" def clause(self): return '1', () + def match(self, item): + return True + class ResultIterator(object): """An iterator into an item query result set.""" diff --git a/bts b/bts index 43cf1faf3..c9a89e90e 100755 --- a/bts +++ b/bts @@ -108,6 +108,8 @@ class BeetsApp(cmdln.Cmdln): parser = cmdln.Cmdln.get_optparser(self) parser.add_option('-l', '--library', dest='libpath', 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 def postoptparse(self): @@ -119,13 +121,17 @@ class BeetsApp(cmdln.Cmdln): self.config.add_section(sec) # Open library file. - libpath = self.options.libpath or self.config.get('beets', 'library') - directory = self.config.get('beets', 'directory') - path_format = self.config.get('beets', 'path_format') - - self.lib = Library(os.path.expanduser(libpath), - directory, - path_format) + 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') + path_format = self.config.get('beets', 'path_format') + self.lib = Library(os.path.expanduser(libpath), + directory, + path_format) @cmdln.alias("imp", "im") def do_import(self, subcmd, opts, *paths):