diff --git a/beets/library.py b/beets/library.py index b85c3989d..51d376b36 100644 --- a/beets/library.py +++ b/beets/library.py @@ -212,7 +212,7 @@ class Item(object): # Dealing with files themselves. - def move(self, library, copy=False, in_album=False): + def move(self, library, copy=False, in_album=False, basedir=None): """Move the item to its designated location within the library directory (provided by destination()). Subdirectories are created as needed. If the operation succeeds, the item's path @@ -225,14 +225,17 @@ class Item(object): it. (This allows items to be moved before they are added to the database, a performance optimization.) - Passes on appropriate exceptions if directories cannot be created - or moving/copying fails. + basedir overrides the library base directory for the + destination. + + Passes on appropriate exceptions if directories cannot be + created or moving/copying fails. Note that one should almost certainly call store() and library.save() after this method in order to keep on-disk data consistent. """ - dest = library.destination(self, in_album=in_album) + dest = library.destination(self, in_album=in_album, basedir=basedir) # Create necessary ancestry for the move. util.mkdirall(dest) @@ -793,13 +796,15 @@ class Library(BaseLibrary): self.conn.executescript(setup_sql) self.conn.commit() - def destination(self, item, pathmod=None, in_album=False, fragment=False): + def destination(self, item, pathmod=None, in_album=False, + fragment=False, basedir=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 method return just the path fragment underneath the root library directory; the path is also returned as Unicode instead of - encoded as a bytestring. + encoded as a bytestring. basedir can override the library's base + directory for the destination. """ pathmod = pathmod or os.path @@ -859,7 +864,8 @@ class Library(BaseLibrary): if fragment: return subpath else: - return normpath(os.path.join(self.directory, subpath)) + basedir = basedir or self.directory + return normpath(os.path.join(basedir, subpath)) # Main interface. @@ -1154,14 +1160,17 @@ class Album(BaseAlbum): (self.id,) ) - def move(self, copy=False): + def move(self, copy=False, basedir=None): """Moves (or copies) all items to their destination. Any - album art moves along with them. + album art moves along with them. basedir overrides the library + base directory for the destination. """ + basedir = basedir or self._library.directory + # Move items. items = list(self.items()) for item in items: - item.move(self._library, copy) + item.move(self._library, copy, basedir=basedir) newdir = os.path.dirname(items[0].path) # Move art. diff --git a/beets/ui/commands.py b/beets/ui/commands.py index 5b47a40f5..456a26ec6 100755 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -958,13 +958,13 @@ def move_items(lib, dest, query, copy, album): entity = 'album' if album else 'item' logging.info('%s %i %ss.' % (action, len(objs), entity)) for obj in objs: - old_path = obj.item_path() if album else obj.path + old_path = obj.item_dir() if album else obj.path logging.debug('moving: %s' % old_path) if album: - obj.move(copy) + obj.move(copy, basedir=dest) else: - obj.move(lib, copy) + obj.move(lib, copy, basedir=dest) lib.store(obj) lib.save() diff --git a/test/test_files.py b/test/test_files.py index 7c28944ad..da175fcb8 100644 --- a/test/test_files.py +++ b/test/test_files.py @@ -45,17 +45,25 @@ class MoveTest(unittest.TestCase, _common.ExtraAsserts): self.i.album = 'two' self.i.title = 'three' self.dest = join(self.libdir, 'one', 'two', 'three.mp3') + + self.otherdir = join(_common.RSRC, 'testotherdir') def tearDown(self): if os.path.exists(self.path): os.remove(self.path) if os.path.exists(self.libdir): shutil.rmtree(self.libdir) + if os.path.exists(self.otherdir): + shutil.rmtree(self.otherdir) def test_move_arrives(self): self.i.move(self.lib) self.assertExists(self.dest) + def test_move_to_custom_dir(self): + self.i.move(self.lib, basedir=self.otherdir) + self.assertExists(join(self.otherdir, 'one', 'two', 'three.mp3')) + def test_move_departs(self): self.i.move(self.lib) self.assertNotExists(self.path) @@ -148,9 +156,13 @@ class AlbumFileTest(unittest.TestCase): touch(self.i.path) # Make an album. self.ai = self.lib.add_album((self.i,)) + # Alternate destination dir. + self.otherdir = os.path.join(_common.RSRC, 'testotherdir') def tearDown(self): if os.path.exists(self.libdir): shutil.rmtree(self.libdir) + if os.path.exists(self.otherdir): + shutil.rmtree(self.otherdir) def test_albuminfo_move_changes_paths(self): self.ai.album = 'newAlbumName' @@ -177,7 +189,12 @@ class AlbumFileTest(unittest.TestCase): self.assertTrue(os.path.exists(oldpath)) self.assertTrue(os.path.exists(self.i.path)) -class ArtFileTest(unittest.TestCase): + def test_albuminfo_move_to_custom_dir(self): + self.ai.move(basedir=self.otherdir) + self.lib.load(self.i) + self.assert_('testotherdir' in self.i.path) + +class ArtFileTest(unittest.TestCase, _common.ExtraAsserts): def setUp(self): # Make library and item. self.lib = beets.library.Library(':memory:') @@ -194,9 +211,13 @@ class ArtFileTest(unittest.TestCase): self.art = self.lib.get_album(self.i).art_destination('something.jpg') touch(self.art) self.ai.artpath = self.art + # Alternate destination dir. + self.otherdir = os.path.join(_common.RSRC, 'testotherdir') def tearDown(self): if os.path.exists(self.libdir): shutil.rmtree(self.libdir) + if os.path.exists(self.otherdir): + shutil.rmtree(self.otherdir) def test_art_deleted_when_items_deleted(self): self.assertTrue(os.path.exists(self.art)) @@ -215,6 +236,17 @@ class ArtFileTest(unittest.TestCase): newart = self.lib.get_album(self.i).art_destination(self.art) self.assertTrue(os.path.exists(newart)) + def test_art_moves_with_album_to_custom_dir(self): + # Move the album to another directory. + self.ai.move(basedir=self.otherdir) + self.lib.load(self.i) + + # Art should be in new directory. + self.assertNotExists(self.art) + newart = self.lib.get_album(self.i).artpath + self.assertExists(newart) + self.assertTrue('testotherdir' in newart) + def test_setart_copies_image(self): newart = os.path.join(self.libdir, 'newart.jpg') touch(newart) diff --git a/test/test_ui.py b/test/test_ui.py index 4c8f8bdd0..33984013b 100644 --- a/test/test_ui.py +++ b/test/test_ui.py @@ -199,6 +199,77 @@ class ModifyTest(unittest.TestCase): item.read() self.assertFalse('newAlbum' in item.path) +class MoveTest(unittest.TestCase, _common.ExtraAsserts): + def setUp(self): + self.io = _common.DummyIO() + self.io.install() + + self.libdir = os.path.join(_common.RSRC, 'testlibdir') + os.mkdir(self.libdir) + + self.itempath = os.path.join(self.libdir, 'srcfile') + shutil.copy(os.path.join(_common.RSRC, 'full.mp3'), self.itempath) + + # Add a file to the library but don't copy it in yet. + self.lib = library.Library(':memory:', self.libdir) + self.i = library.Item.from_path(self.itempath) + self.lib.add(self.i, False) + self.album = self.lib.add_album([self.i]) + + # Alternate destination directory. + self.otherdir = os.path.join(_common.RSRC, 'testotherdir') + + def tearDown(self): + self.io.restore() + shutil.rmtree(self.libdir) + if os.path.exists(self.otherdir): + shutil.rmtree(self.otherdir) + + def _move(self, query=(), dest=None, copy=False, album=False): + commands.move_items(self.lib, dest, query, copy, album) + + def test_move_item(self): + self._move() + self.lib.load(self.i) + 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.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.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.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.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.assertTrue('testotherdir' in self.i.path) + self.assertExists(self.i.path) + self.assertNotExists(self.itempath) + class UpdateTest(unittest.TestCase, _common.ExtraAsserts): def setUp(self): self.io = _common.DummyIO()