diff --git a/beets/importer.py b/beets/importer.py index a5371fabf..398f5fcf6 100644 --- a/beets/importer.py +++ b/beets/importer.py @@ -811,7 +811,7 @@ def plugin_stage(session, func): # Stage may modify DB, so re-load cached item data. for item in task.imported_items(): - session.lib.load(item) + item.load() def manipulate_files(session): """A coroutine (pipeline stage) that performs necessary file @@ -858,7 +858,7 @@ def manipulate_files(session): # Save new paths. with session.lib.transaction(): for item in items: - session.lib.store(item) + item.store() # Plugin event. plugins.send('import_task_files', session=session, task=task) diff --git a/beets/library.py b/beets/library.py index d254f731c..ef6f86e85 100644 --- a/beets/library.py +++ b/beets/library.py @@ -1405,6 +1405,8 @@ class Library(object): item.added = time.time() if copy: self.move(item, copy=True) + if not item._lib: + item._lib = self # Build essential parts of query. columns = ','.join([key for key in ITEM_KEYS if key != 'id']) @@ -1438,49 +1440,6 @@ class Library(object): self._memotable = {} return new_id - def load(self, item): - """Refresh the item's metadata from the library database. - """ - if item.id is None: - raise ValueError('cannot load item with no id') - stored_item = self.get_item(item.id) - item.update(dict(stored_item)) - item.clear_dirty() - - def store(self, item): - """Save the item's metadata into the library database. - """ - # Build assignments for query. - assignments = '' - subvars = [] - for key in ITEM_KEYS: - if key != 'id' and key in item._dirty: - assignments += key + '=?,' - value = getattr(item, key) - # Wrap path strings in buffers so they get stored - # "in the raw". - if key == 'path' and isinstance(value, str): - value = buffer(value) - subvars.append(value) - assignments = assignments[:-1] # Knock off last , - - with self.transaction() as tx: - # Main table update. - if assignments: - query = 'UPDATE items SET ' + assignments + ' WHERE id=?' - subvars.append(item.id) - tx.mutate(query, subvars) - - # Flexible attributes. - flexins = 'INSERT INTO item_attributes ' \ - ' (entity_id, key, value)' \ - ' VALUES (?, ?, ?)' - for key, value in item._values_flex.items(): - tx.mutate(flexins, (item.id, key, value)) - - item.clear_dirty() - self._memotable = {} - def remove(self, item, delete=False, with_album=True): """Removes this item. If delete, then the associated file is removed from disk. If with_album, then the item's album (if any) @@ -1531,7 +1490,7 @@ class Library(object): old_path = item.path item.move(dest, copy) if item.id is not None: - self.store(item) + item.store() # If this item is in an album, move its art. if with_album: @@ -1648,7 +1607,7 @@ class Library(object): if item.id is None: self.add(item) else: - self.store(item) + item.store() # Construct the new Album object. album_values['id'] = album_id diff --git a/beets/ui/commands.py b/beets/ui/commands.py index c835b8bb8..70f59c5f4 100644 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -941,14 +941,14 @@ def update_items(lib, query, album, move, pretend): if move and lib.directory in ancestry(item.path): lib.move(item) - lib.store(item) + item.store() affected_albums.add(item.album_id) elif not pretend: # The file's mtime was different, but there were no changes # to the metadata. Store the new mtime, which is set in the # call to read(), so we don't check this again in the # future. - lib.store(item) + item.store() # Skip album changes while pretending. if pretend: @@ -1163,10 +1163,7 @@ def modify_items(lib, mods, query, write, move, album, confirm): else: lib.move(obj) - if album: - obj.store() - else: - lib.store(obj) + obj.store() # Apply tags if requested. if write: @@ -1223,7 +1220,7 @@ def move_items(lib, dest, query, copy, album): obj.move(copy, basedir=dest) else: lib.move(obj, copy, basedir=dest) - lib.store(obj) + obj.store() move_cmd = ui.Subcommand('move', help='move or copy items', aliases=('mv',)) diff --git a/beetsplug/chroma.py b/beetsplug/chroma.py index 83f67c0a9..084540269 100644 --- a/beetsplug/chroma.py +++ b/beetsplug/chroma.py @@ -160,7 +160,7 @@ class AcoustidPlugin(plugins.BeetsPlugin): help='generate fingerprints for items without them') def fingerprint_cmd_func(lib, opts, args): for item in lib.items(ui.decargs(args)): - fingerprint_item(item, lib=lib, + fingerprint_item(item, write=config['import']['write'].get(bool)) fingerprint_cmd.func = fingerprint_cmd_func @@ -237,12 +237,12 @@ def submit_items(userkey, items, chunksize=64): submit_chunk() -def fingerprint_item(item, lib=None, write=False): +def fingerprint_item(item, write=False): """Get the fingerprint for an Item. If the item already has a fingerprint, it is not regenerated. If fingerprint generation fails, - return None. If `lib` is provided, then new fingerprints are saved - to the database. If `write` is set, then the new fingerprints are - also written to files' metadata. + return None. If the items are associated with a library, they are + saved to the database. If `write` is set, then the new fingerprints + are also written to files' metadata. """ # Get a fingerprint and length for this track. if not item.length: @@ -271,8 +271,8 @@ def fingerprint_item(item, lib=None, write=False): util.displayable_path(item.path) )) item.write() - if lib: - lib.store(item) + if item._lib: + item.store() return item.acoustid_fingerprint except acoustid.FingerprintGenerationError as exc: log.info( diff --git a/beetsplug/convert.py b/beetsplug/convert.py index 1bb81ae4f..f7e922019 100644 --- a/beetsplug/convert.py +++ b/beetsplug/convert.py @@ -120,7 +120,7 @@ def convert_item(lib, dest_dir, keep_new, path_formats): # writing) to get new bitrate, duration, etc. if keep_new: item.read() - lib.store(item) # Store new path and audio data. + item.store() # Store new path and audio data. if config['convert']['embed']: album = lib.get_album(item) @@ -142,7 +142,7 @@ def convert_on_import(lib, item): item.path = dest item.write() item.read() # Load new audio information data. - lib.store(item) + item.store() def convert_func(lib, opts, args): diff --git a/beetsplug/echonest_tempo.py b/beetsplug/echonest_tempo.py index d3a55d213..d5026c021 100644 --- a/beetsplug/echonest_tempo.py +++ b/beetsplug/echonest_tempo.py @@ -54,7 +54,7 @@ def fetch_item_tempo(lib, loglevel, item, write): item.bpm = tempo if write: item.write() - lib.store(item) + item.store() def get_tempo(artist, title): """Get the tempo for a song.""" diff --git a/beetsplug/lastgenre/__init__.py b/beetsplug/lastgenre/__init__.py index e26ade1fc..dbfa92224 100644 --- a/beetsplug/lastgenre/__init__.py +++ b/beetsplug/lastgenre/__init__.py @@ -335,7 +335,7 @@ class LastGenrePlugin(plugins.BeetsPlugin): # track on the album. if 'track' in self.sources: item.genre, src = self._get_genre(item) - lib.store(item) + item.store() log.info(u'genre for track {0} - {1} ({2}): {3}'.format( item.artist, item.title, src, item.genre )) @@ -353,17 +353,18 @@ class LastGenrePlugin(plugins.BeetsPlugin): album.genre, src = self._get_genre(album) log.debug(u'added last.fm album genre ({0}): {1}'.format( src, album.genre)) + album.store() if 'track' in self.sources: for item in album.items(): item.genre, src = self._get_genre(item) log.debug(u'added last.fm item genre ({0}): {1}'.format( src, item.genre)) - session.lib.store(item) + item.store() else: item = task.item item.genre, src = self._get_genre(item) log.debug(u'added last.fm item genre ({0}): {1}'.format( src, item.genre)) - session.lib.store(item) + item.store() diff --git a/beetsplug/lyrics.py b/beetsplug/lyrics.py index bc71a5a89..a6d5b7e7a 100644 --- a/beetsplug/lyrics.py +++ b/beetsplug/lyrics.py @@ -444,7 +444,7 @@ class LyricsPlugin(BeetsPlugin): if write: item.write() - lib.store(item) + item.store() def get_lyrics(self, artist, title): """Fetch lyrics, trying each source in turn. Return a string or diff --git a/beetsplug/mbsync.py b/beetsplug/mbsync.py index 56141d924..140cb95c1 100644 --- a/beetsplug/mbsync.py +++ b/beetsplug/mbsync.py @@ -53,7 +53,7 @@ def _print_and_apply_changes(lib, item, move, pretend, write): log.error(u'could not sync {0}: {1}'.format( util.displayable_path(item.path), exc)) return False - lib.store(item) + item.store() return True diff --git a/beetsplug/replaygain.py b/beetsplug/replaygain.py index ba6bae862..6b4c2f407 100644 --- a/beetsplug/replaygain.py +++ b/beetsplug/replaygain.py @@ -225,7 +225,7 @@ class ReplayGainPlugin(BeetsPlugin): for item, info in zip(items, rgain_infos): item.rg_track_gain = info['gain'] item.rg_track_peak = info['peak'] - lib.store(item) + item.store() log.debug(u'replaygain: applied track gain {0}, peak {1}'.format( item.rg_track_gain, @@ -241,3 +241,4 @@ class ReplayGainPlugin(BeetsPlugin): album.rg_album_gain, album.rg_album_peak )) + album.store() diff --git a/test/test_db.py b/test/test_db.py index 744933049..0647a9b8c 100644 --- a/test/test_db.py +++ b/test/test_db.py @@ -40,7 +40,7 @@ def remove_lib(): if os.path.exists(TEMP_LIB): os.unlink(TEMP_LIB) def boracay(l): - return beets.library.Item( + return beets.library.Item(l, **l._connection().execute('select * from items where id=3').fetchone() ) np = util.normpath @@ -56,12 +56,12 @@ class LoadTest(unittest.TestCase): def test_load_restores_data_from_db(self): original_title = self.i.title self.i.title = 'something' - self.lib.load(self.i) + self.i.load() self.assertEqual(original_title, self.i.title) def test_load_clears_dirty_flags(self): self.i.artist = 'something' - self.lib.load(self.i) + self.i.load() self.assertTrue('artist' not in self.i._dirty) class StoreTest(unittest.TestCase): @@ -74,7 +74,7 @@ class StoreTest(unittest.TestCase): def test_store_changes_database_value(self): self.i.year = 1987 - self.lib.store(self.i) + self.i.store() new_year = self.lib._connection().execute( 'select year from items where ' 'title="Boracay"').fetchone()['year'] @@ -83,7 +83,7 @@ class StoreTest(unittest.TestCase): def test_store_only_writes_dirty_fields(self): original_genre = self.i.genre self.i._values_fixed['genre'] = 'beatboxing' # change w/o dirtying - self.lib.store(self.i) + self.i.store() new_genre = self.lib._connection().execute( 'select genre from items where ' 'title="Boracay"').fetchone()['genre'] @@ -91,7 +91,7 @@ class StoreTest(unittest.TestCase): def test_store_clears_dirty_flags(self): self.i.composer = 'tvp' - self.lib.store(self.i) + self.i.store() self.assertTrue('composer' not in self.i._dirty) class AddTest(unittest.TestCase): @@ -884,7 +884,7 @@ class PathStringTest(_common.TestCase): def test_special_chars_preserved_in_database(self): path = 'b\xe1r' self.i.path = path - self.lib.store(self.i) + self.i.store() i = list(self.lib.items())[0] self.assertEqual(i.path, path) diff --git a/test/test_files.py b/test/test_files.py index ff887637c..03588a706 100644 --- a/test/test_files.py +++ b/test/test_files.py @@ -171,7 +171,7 @@ class AlbumFileTest(_common.TestCase): self.ai.album = 'newAlbumName' self.ai.move() self.ai.store() - self.lib.load(self.i) + self.i.load() self.assert_('newAlbumName' in self.i.path) @@ -180,7 +180,7 @@ class AlbumFileTest(_common.TestCase): self.ai.album = 'newAlbumName' self.ai.move() self.ai.store() - self.lib.load(self.i) + self.i.load() self.assertFalse(os.path.exists(oldpath)) self.assertTrue(os.path.exists(self.i.path)) @@ -190,14 +190,14 @@ class AlbumFileTest(_common.TestCase): self.ai.album = 'newAlbumName' self.ai.move(True) self.ai.store() - self.lib.load(self.i) + self.i.load() self.assertTrue(os.path.exists(oldpath)) self.assertTrue(os.path.exists(self.i.path)) def test_albuminfo_move_to_custom_dir(self): self.ai.move(basedir=self.otherdir) - self.lib.load(self.i) + self.i.load() self.ai.store() self.assertTrue('testotherdir' in self.i.path) @@ -234,7 +234,7 @@ class ArtFileTest(_common.TestCase): oldpath = self.i.path self.ai.album = 'newAlbum' self.ai.move() - self.lib.load(self.i) + self.i.load() self.assertNotEqual(self.i.path, oldpath) self.assertFalse(os.path.exists(self.art)) @@ -245,7 +245,7 @@ class ArtFileTest(_common.TestCase): # Move the album to another directory. self.ai.move(basedir=self.otherdir) self.ai.store() - self.lib.load(self.i) + self.i.load() # Art should be in new directory. self.assertNotExists(self.art) diff --git a/test/test_ihate.py b/test/test_ihate.py index 3ff6dcd8d..9c241fc54 100644 --- a/test/test_ihate.py +++ b/test/test_ihate.py @@ -16,7 +16,7 @@ class IHatePluginTest(unittest.TestCase): task = ImportTask() task.cur_artist = u'Test Artist' task.cur_album = u'Test Album' - task.items = [Item({'genre': 'Test Genre'})] + task.items = [Item(genre='Test Genre')] self.assertFalse(IHatePlugin.do_i_hate_this(task, genre_p, artist_p, album_p, white_p)) genre_p = 'some_genre test\sgenre'.split() diff --git a/test/test_importer.py b/test/test_importer.py index 4d159b504..34a9e3f0e 100644 --- a/test/test_importer.py +++ b/test/test_importer.py @@ -193,6 +193,7 @@ class ImportApplyTest(_common.TestCase): shutil.copy(os.path.join(_common.RSRC, 'full.mp3'), self.srcpath) self.i = library.Item.from_path(self.srcpath) self.i.comp = False + self.lib.add(self.i) trackinfo = TrackInfo('one', 'trackid', 'some artist', 'artistid', 1) diff --git a/test/test_query.py b/test/test_query.py index 885a4506c..996e341b4 100644 --- a/test/test_query.py +++ b/test/test_query.py @@ -299,7 +299,7 @@ class MemoryGetTest(unittest.TestCase, AssertsMixin): def test_unicode_query(self): self.single_item.title = u'caf\xe9' - self.lib.store(self.single_item) + self.single_item.store() q = u'title:caf\xe9' results = self.lib.items(q) diff --git a/test/test_ui.py b/test/test_ui.py index 4f1a6a6e6..868c4b6b3 100644 --- a/test/test_ui.py +++ b/test/test_ui.py @@ -54,7 +54,7 @@ class ListTest(_common.TestCase): def test_list_unicode_query(self): self.item.title = u'na\xefve' - self.lib.store(self.item) + self.item.store() self.lib._connection().commit() self._run_list([u'na\xefve']) @@ -241,42 +241,42 @@ class MoveTest(_common.TestCase): def test_move_item(self): self._move() - self.lib.load(self.i) + self.i.load() self.assertTrue('testlibdir' in self.i.path) self.assertExists(self.i.path) self.assertNotExists(self.itempath) def test_copy_item(self): self._move(copy=True) - self.lib.load(self.i) + self.i.load() self.assertTrue('testlibdir' in self.i.path) self.assertExists(self.i.path) self.assertExists(self.itempath) def test_move_album(self): self._move(album=True) - self.lib.load(self.i) + self.i.load() self.assertTrue('testlibdir' in self.i.path) self.assertExists(self.i.path) self.assertNotExists(self.itempath) def test_copy_album(self): self._move(copy=True, album=True) - self.lib.load(self.i) + self.i.load() self.assertTrue('testlibdir' in self.i.path) self.assertExists(self.i.path) self.assertExists(self.itempath) def test_move_item_custom_dir(self): self._move(dest=self.otherdir) - self.lib.load(self.i) + self.i.load() self.assertTrue('testotherdir' in self.i.path) self.assertExists(self.i.path) self.assertNotExists(self.itempath) def test_move_album_custom_dir(self): self._move(dest=self.otherdir, album=True) - self.lib.load(self.i) + self.i.load() self.assertTrue('testotherdir' in self.i.path) self.assertExists(self.i.path) self.assertNotExists(self.itempath) @@ -306,7 +306,7 @@ class UpdateTest(_common.TestCase): self.io.addinput('y') if reset_mtime: self.i.mtime = 0 - self.lib.store(self.i) + self.i.store() commands.update_items(self.lib, query, album, move, False) def test_delete_removes_item(self): @@ -376,7 +376,7 @@ class UpdateTest(_common.TestCase): # Make in-memory mtime match on-disk mtime. self.i.mtime = os.path.getmtime(self.i.path) - self.lib.store(self.i) + self.i.store() self._update(reset_mtime=False) item = self.lib.items().get()