diff --git a/beets/library.py b/beets/library.py index a3b408236..6c5a6cde2 100644 --- a/beets/library.py +++ b/beets/library.py @@ -952,7 +952,38 @@ class Library(BaseLibrary): if delete: util.soft_remove(item.path) util.prune_dirs(os.path.dirname(item.path), self.directory) - + + def update(self, item, move=True): + """Reads the item's metadata from file, and updates the library. If + move, then the files will be moved to reflect the changes. + """ + old_album = self.get_album(item) + + item.read() + + new_album = self.get_album_by_item_properties(item) + if new_album is None: + # No existing album matching the new metadata, so we create one + new_album = self.add_album((item,)) + if move and old_album and old_album.artpath: + new_album.set_art(old_album.artpath) + item.album_id = new_album.id + + old_path = item.path + if move: + item.move(self) + + self.store(item) + + # Delete old album if it's empty + if move and old_album: + item_iter = old_album.items() + try: + item_iter.next() + except StopIteration: + # Album is empty. + old_album.remove(True, False) + util.prune_dirs(os.path.dirname(old_path), self.directory) # Querying. @@ -1061,6 +1092,30 @@ class Library(BaseLibrary): self.store(item) return album + + def get_album_by_item_properties(self, item): + """Given an item, return an Album object that matches all the item's + fields listed in ALBUM_KEYS_ITEM. If no such album exists, returns + None.""" + item_values = dict( + (key, getattr(item, key)) for key in ALBUM_KEYS_ITEM) + + queries = [] + for key in ALBUM_KEYS_ITEM: + queries.append(MatchQuery(key, item_values[key])) + super_query = AndQuery(queries) + where, subvals = super_query.clause() + + sql = "SELECT * FROM albums " + \ + "WHERE " + where + \ + " ORDER BY albumartist, album" + c = self.conn.execute(sql, subvals) + try: + record = c.fetchone() + finally: + c.close() + if record: + return Album(self, dict(record)) class Album(BaseAlbum): """Provides access to information about albums stored in a @@ -1145,6 +1200,10 @@ class Album(BaseAlbum): 'DELETE FROM albums WHERE id=?', (self.id,) ) + + def update(self): + for item in self.items(): + self._library.update(item) def move(self, copy=False): """Moves (or copies) all items to their destination. Any diff --git a/beets/ui/commands.py b/beets/ui/commands.py index 080cade5f..0a344d916 100755 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -627,6 +627,51 @@ list_cmd.func = list_func default_commands.append(list_cmd) +# update: Query and update library contents. + +def update_items(lib, query, album, path): + """Print out items in lib matching query. If album, then search for + albums instead of single items. If path, print the matched objects' + paths instead of human-readable information about them. + """ + # Get the matching items. + if album: + albums = list(lib.albums(query)) + items = [] + for al in albums: + items += al.items() + else: + items = list(lib.items(query)) + + if not items: + print_('No matching items found.') + return + + # Show all the items. + for item in items: + print_(item.artist + ' - ' + item.album + ' - ' + item.title) + + # Remove (and possibly delete) items. + if album: + for al in albums: + al.update() + else: + for item in items: + lib.update(item) + + lib.save() + +update_cmd = ui.Subcommand('update', help='update the library', aliases=('upd','up',)) +update_cmd.parser.add_option('-a', '--album', action='store_true', + help='show matching albums instead of tracks') +update_cmd.parser.add_option('-p', '--path', action='store_true', + help='print paths for matched items or albums') +def update_func(lib, config, opts, args): + update_items(lib, decargs(args), opts.album, opts.path) +update_cmd.func = update_func +default_commands.append(update_cmd) + + # remove: Remove items from library, delete files. def remove_items(lib, query, album, delete=False):