diff --git a/beets/__init__.py b/beets/__init__.py index 72fbdaee5..140d86ddf 100644 --- a/beets/__init__.py +++ b/beets/__init__.py @@ -1 +1,2 @@ -from beets.library import Library +import beets.library +Library = beets.library.Library diff --git a/beets/library.py b/beets/library.py index 0baad18c1..fe04603a0 100644 --- a/beets/library.py +++ b/beets/library.py @@ -105,7 +105,8 @@ class Item(object): self.dirty[key] = False def __repr__(self): - return 'Item(' + repr(self.record) + ', library=' + self.library + ')' + return 'Item(' + repr(self.record) + \ + ', library=' + repr(self.library) + ')' #### item field accessors #### @@ -358,15 +359,26 @@ class Query(object): c = library.conn.cursor() c.execute(*self.statement()) return ResultIterator(c, library) - -class SubstringQuery(Query): - """A query that matches a substring in a specific item field.""" + +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') self.field = field self.pattern = pattern + +class MatchQuery(FieldQuery): + """A query that looks for exact matches in an item field.""" + + def clause(self): + return self.field + " = ?", [self.pattern] + +class SubstringQuery(FieldQuery): + """A query that matches a substring in a specific item field.""" def clause(self): search = '%' + (self.pattern.replace('\\','\\\\').replace('%','\\%') @@ -490,12 +502,22 @@ class ResultIterator(object): def __iter__(self): return self + def count(self): + """Returns the number of matched rows and invalidates the + iterator.""" + # Apparently, there is no good way to get the number of rows + # returned by an sqlite SELECT. + num = 0 + for i in self: + num += 1 + return num + def next(self): try: row = self.cursor.next() except StopIteration: self.cursor.close() - raise StopIteration + raise return Item(row, self.library) diff --git a/beets/player/bpd.py b/beets/player/bpd.py index ddc16c632..1161a559e 100755 --- a/beets/player/bpd.py +++ b/beets/player/bpd.py @@ -8,7 +8,7 @@ use of the wide range of MPD clients. import eventlet.api import re from string import Template -from beets import Library +import beets import sys import traceback @@ -152,8 +152,8 @@ class Server(object): returns its index in the playlist. """ track_id = cast_arg(int, track_id) - for index, track in self.playlist: - if _item_id(track) == track_id: + for index, track in enumerate(self.playlist): + if self._item_id(track) == track_id: return index # Loop finished with no track found. raise ArgumentNotFoundError() @@ -311,10 +311,19 @@ class Server(object): track = self.playlist[index] except IndexError: raise ArgumentIndexError() - return SuccessReponse(self._item_info(track)) + return SuccessResponse(self._item_info(track)) def cmd_playlistid(self, track_id=-1): return self.cmd_playlistinfo(self._id_to_index(track_id)) + def cmd_plchanges(self, version): + """Returns playlist changes since the given version. + + This is a "fake" implementation that ignores the version and + just returns the entire playlist (rather like version=0). This + seems to satisfy many clients. + """ + return self.cmd_playlistinfo() + def cmd_currentsong(self): """Returns information about the currently-playing song. """ @@ -354,9 +363,13 @@ class Server(object): self.current_index = index self.paused = False - + def cmd_playid(self, track_id=0): - index = self._id_to_index(track_id) + track_id = cast_arg(int, track_id) + if track_id == -1: + index = -1 + else: + index = self._id_to_index(track_id) self.cmd_play(index) def cmd_stop(self): @@ -525,7 +538,6 @@ class CommandList(list): for i, command in enumerate(self): resp = command.run(server) - print resp.items out.extend(resp.items) # If the command failed, stop executing and send the completion @@ -640,16 +652,53 @@ class BGServer(Server): return item.id def cmd_lsinfo(self, path="/"): + """Return info on all the items in the path.""" if path != "/": raise BPDError(ERROR_NO_EXIST, 'cannot list paths other than /') return self._items_info(self.lib.get()) + def cmd_listallinfo(self, path="/"): + """Return info on all the items in the directory, recursively.""" + # Because we have a flat directory path, this recursive version + # is equivalent to the non-recursive version. + return self.cmd_lsinfo(path) + def cmd_listall(self, path="/"): + """Return the paths all items in the directory, recursively.""" + if path != "/": + raise BPDError(ERROR_NO_EXIST, 'cannot list paths other than /') + out = ['file: ' + i.path for i in self.lib.get()] + return SuccessResponse(out) def cmd_search(self, key, value): + """Perform a substring match in a specific column.""" if key == 'filename': key = 'path' - query = key + ':' + value + '' + query = beets.library.SubstringQuery(key, value) return self._items_info(self.lib.get(query)) + + def cmd_find(self, key, value): + """Perform an exact match in a specific column.""" + if key == 'filename': + key = 'path' + query = beets.library.MatchQuery(key, value) + return self._items_info(self.lib.get(query)) + + def _get_by_path(self, path): + it = self.lib.get(beets.library.MatchQuery('path', path)) + try: + return it.next() + except StopIteration: + raise ArgumentNotFoundError() + def cmd_add(self, path): + """Adds a track to the playlist, specified by its path.""" + self.playlist.append(self._get_by_path(path)) + self.playlist_version += 1 + def cmd_addid(self, path): + """Same as cmd_add but returns an id.""" + track = self._get_by_path(path) + self.playlist.append(track) + self.playlist_version += 1 + return SuccessResponse(['Id: ' + str(track.id)]) if __name__ == '__main__': - BGServer(Library('library.blb')).run() + BGServer(beets.Library('library.blb')).run()