add items to DB before moving/copying (#190)

Previously, all files would be moved/copied/deleted before the corresponding
Items and Albums were added to the database. Now, the in-place items are added
to the database; the files are moved; and then the new paths are saved to the
DB. The apply_choices coroutine now executes two database transactions per task.
This has a couple of benefits:
- %aunique{} requires album structures to be in place before the destination()
  call, so this now works as expected.
- As an added bonus, the "in_album" parameter to move() and destination() --
  along with its associated ugly hacks -- is no longer required.
This commit is contained in:
Adrian Sampson 2012-04-29 14:14:11 -07:00
parent 4b253df48c
commit 2087ff6e41
3 changed files with 37 additions and 42 deletions

View file

@ -702,29 +702,9 @@ def apply_choices(config):
util.prune_dirs(os.path.dirname(duplicate_path),
lib.directory)
# Move/copy files.
task.old_paths = [item.path for item in items] # For deletion.
for item in items:
if config.copy or config.move:
if config.move:
# Just move the file.
lib.move(item, False, task.is_album)
else:
# If it's a reimport, move the file. Otherwise, copy
# and keep track of the old path.
old_path = item.path
do_copy = not bool(replaced_items[item])
lib.move(item, do_copy, task.is_album)
if not do_copy:
# If we moved the item, remove the now-nonexistent
# file from old_paths.
task.old_paths.remove(old_path)
if config.write and task.should_write_tags():
item.write()
# Add items to library. We consolidate this at the end to avoid
# locking while we do the copying and tag updates.
# Add items -- before path changes -- to the library. We add the
# items now (rather than at the end) so that album structures
# are in place before calls to destination().
try:
# Remove old items.
for replaced in replaced_items.itervalues():
@ -745,6 +725,34 @@ def apply_choices(config):
finally:
lib.save()
# Move/copy files.
task.old_paths = [item.path for item in items] # For deletion.
for item in items:
if config.copy or config.move:
if config.move:
# Just move the file.
lib.move(item, False)
else:
# If it's a reimport, move the file. Otherwise, copy
# and keep track of the old path.
old_path = item.path
do_copy = not bool(replaced_items[item])
lib.move(item, do_copy)
if not do_copy:
# If we moved the item, remove the now-nonexistent
# file from old_paths.
task.old_paths.remove(old_path)
if config.write and task.should_write_tags():
item.write()
# Save new paths.
try:
for item in items:
lib.store(item)
finally:
lib.save()
def fetch_art(config):
"""A coroutine that fetches and applies album art for albums where
appropriate.

View file

@ -829,11 +829,10 @@ class Library(BaseLibrary):
self.conn.executescript(setup_sql)
self.conn.commit()
def destination(self, item, pathmod=None, in_album=False,
fragment=False, basedir=None, platform=None):
def destination(self, item, pathmod=None, fragment=False,
basedir=None, platform=None):
"""Returns the path in the library directory designated for item
item (i.e., where the file ought to be). in_album forces the
item to be treated as part of an album. fragment makes this
item (i.e., where the file ought to be). fragment makes this
method return just the path fragment underneath the root library
directory; the path is also returned as Unicode instead of
encoded as a bytestring. basedir can override the library's base
@ -848,14 +847,6 @@ class Library(BaseLibrary):
if query == PF_KEY_DEFAULT:
continue
query = AndQuery.from_string(query)
if in_album:
# If we're treating this item as a member of the item,
# hack the query so that singleton queries always
# observe the item to be non-singleton.
for i, subquery in enumerate(query):
if isinstance(subquery, SingletonQuery):
query[i] = FalseQuery() if subquery.sense \
else TrueQuery()
if query.match(item):
# The query matches the item! Use the corresponding path
# format.
@ -1024,7 +1015,7 @@ class Library(BaseLibrary):
util.soft_remove(item.path)
util.prune_dirs(os.path.dirname(item.path), self.directory)
def move(self, item, copy=False, in_album=False, basedir=None,
def move(self, item, copy=False, basedir=None,
with_album=True):
"""Move the item to its designated location within the library
directory (provided by destination()). Subdirectories are
@ -1033,11 +1024,6 @@ class Library(BaseLibrary):
If copy is True, moving the file is copied rather than moved.
If in_album is True, then the track is treated as part of an
album even if it does not yet have an album_id associated with
it. (This allows items to be moved before they are added to the
database, a performance optimization.)
basedir overrides the library base directory for the
destination.
@ -1050,7 +1036,7 @@ class Library(BaseLibrary):
side effect. You probably want to call save() to commit the DB
transaction.
"""
dest = self.destination(item, in_album=in_album, basedir=basedir)
dest = self.destination(item, basedir=basedir)
# Create necessary ancestry for the move.
util.mkdirall(dest)

View file

@ -46,6 +46,7 @@ Changelog
even on Unix platforms (this causes less surprise when using Samba shares to
store music). To customize your character substitutions, see :ref:`the replace
config option <replace>`.
* Filename collisions are now avoided when moving album art.
* :doc:`/plugins/bpd`: Use Gstreamer's ``playbin2`` element instead of the
deprecated ``playbin``.
* :doc:`/plugins/bpd`: Listings are now sorted (thanks once again to Matteo