diff --git a/beets/ui/commands.py b/beets/ui/commands.py index 995ff87e9..96cd08b6b 100644 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -1450,7 +1450,8 @@ default_commands.append(modify_cmd) # move: Move/copy files to the library or a new base directory. -def move_items(lib, dest, query, copy, album, pretend, confirm=False): +def move_items(lib, dest, query, copy, album, pretend, confirm=False, + export=False): """Moves or copies items to a new base directory, given by dest. If dest is None, then the library's base directory is used, making the command "consolidate" files. @@ -1463,6 +1464,7 @@ def move_items(lib, dest, query, copy, album, pretend, confirm=False): isalbummoved = lambda album: any(isitemmoved(i) for i in album.items()) objs = [o for o in objs if (isalbummoved if album else isitemmoved)(o)] + copy = copy or export # Exporting always copies. action = u'Copying' if copy else u'Moving' act = u'copy' if copy else u'move' entity = u'album' if album else u'item' @@ -1488,8 +1490,12 @@ def move_items(lib, dest, query, copy, album, pretend, confirm=False): for obj in objs: log.debug(u'moving: {0}', util.displayable_path(obj.path)) - obj.move(copy, basedir=dest) - obj.store() + if export: + # Copy without affecting the database. + obj.move(True, basedir=dest, store=False) + else: + # Ordinary move/copy: store the new path. + obj.move(copy, basedir=dest) def move_func(lib, opts, args): @@ -1500,7 +1506,7 @@ def move_func(lib, opts, args): raise ui.UserError(u'no such directory: %s' % dest) move_items(lib, dest, decargs(args), opts.copy, opts.album, opts.pretend, - opts.timid) + opts.timid, opts.export) move_cmd = ui.Subcommand( @@ -1522,6 +1528,10 @@ move_cmd.parser.add_option( u'-t', u'--timid', dest='timid', action='store_true', help=u'always confirm all actions' ) +move_cmd.parser.add_option( + u'-e', u'--export', default=False, action='store_true', + help=u'copy without changing the database path' +) move_cmd.parser.add_album_option() move_cmd.func = move_func default_commands.append(move_cmd) diff --git a/docs/reference/cli.rst b/docs/reference/cli.rst index b4c7b9e1b..e830d1482 100644 --- a/docs/reference/cli.rst +++ b/docs/reference/cli.rst @@ -275,6 +275,7 @@ query are renamed into your library directory structure. By specifying a destination directory with ``-d`` manually, you can move items matching a query anywhere in your filesystem. The ``-c`` option copies files instead of moving them. As with other commands, the ``-a`` option matches albums instead of items. +The ``-e`` flag (for "export") copies files without changing the database. To perform a "dry run", just use the ``-p`` (for "pretend") flag. This will show you a list of files that would be moved but won't actually change anything diff --git a/test/test_ui.py b/test/test_ui.py index c519e66fb..04c033f51 100644 --- a/test/test_ui.py +++ b/test/test_ui.py @@ -421,8 +421,9 @@ class MoveTest(_common.TestCase): self.otherdir = os.path.join(self.temp_dir, b'testotherdir') def _move(self, query=(), dest=None, copy=False, album=False, - pretend=False): - commands.move_items(self.lib, dest, query, copy, album, pretend) + pretend=False, export=False): + commands.move_items(self.lib, dest, query, copy, album, pretend, + export=export) def test_move_item(self): self._move() @@ -476,6 +477,24 @@ class MoveTest(_common.TestCase): self.i.load() self.assertIn(b'srcfile', self.i.path) + def test_export_item_custom_dir(self): + self._move(dest=self.otherdir, export=True) + self.i.load() + self.assertEqual(self.i.path, self.itempath) + self.assertExists(self.otherdir) + + def test_export_album_custom_dir(self): + self._move(dest=self.otherdir, album=True, export=True) + self.i.load() + self.assertEqual(self.i.path, self.itempath) + self.assertExists(self.otherdir) + + def test_pretend_export_item(self): + self._move(dest=self.otherdir, pretend=True, export=True) + self.i.load() + self.assertIn(b'srcfile', self.i.path) + self.assertNotExists(self.otherdir) + class UpdateTest(_common.TestCase): def setUp(self):