From 2db346388a61a5fd71fbebb877a202d557ed7b65 Mon Sep 17 00:00:00 2001 From: Malte Ried Date: Sun, 21 Dec 2014 15:56:56 +0100 Subject: [PATCH 1/4] Added option --pretend to only print the filenames of files to import without importing them --- beets/importer.py | 33 ++++++++++++++++++++++----------- beets/ui/commands.py | 4 ++++ docs/reference/cli.rst | 6 +++++- test/test_importer.py | 41 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 72 insertions(+), 12 deletions(-) diff --git a/beets/importer.py b/beets/importer.py index e8e28b3bd..4844fc128 100644 --- a/beets/importer.py +++ b/beets/importer.py @@ -34,6 +34,7 @@ from beets import dbcore from beets import plugins from beets import util from beets import config +from beets.ui import print_ from beets.util import pipeline, sorted_walk, ancestry from beets.util import syspath, normpath, displayable_path from enum import Enum @@ -960,6 +961,8 @@ class ImportTaskFactory(object): self.toppath = toppath self.session = session self.skipped = 0 + self.pretend = session.config[ + 'pretend'] if 'pretend' in session.config else False def tasks(self): """Yield all import tasks for `self.toppath`. @@ -970,15 +973,22 @@ class ImportTaskFactory(object): for dirs, paths in self.paths(): if self.session.config['singletons']: for path in paths: - task = self.singleton(path) - if task: - yield task + if self.pretend: + print_(displayable_path(path)) + else: + task = self.singleton(path) + if task: + yield task yield self.sentinel(dirs) else: - task = self.album(paths, dirs) - if task: - yield task + if self.pretend: + for path in paths: + print_(displayable_path(path)) + else: + task = self.album(paths, dirs) + if task: + yield task def paths(self): """Walk `self.toppath` and yield pairs of directory lists and @@ -1101,12 +1111,13 @@ def read_tasks(session): # Indicate the directory is finished. # FIXME hack to delete extracted archives - if archive_task is None: - yield task_factory.sentinel() - else: - yield archive_task + if not task_factory.pretend: + if archive_task is None: + yield task_factory.sentinel() + else: + yield archive_task - if not imported: + if not imported and not task_factory.pretend: log.warn(u'No files imported from {0}' .format(displayable_path(user_toppath))) diff --git a/beets/ui/commands.py b/beets/ui/commands.py index 910fe0243..69f7cb898 100644 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -943,6 +943,10 @@ import_cmd.parser.add_option( '-g', '--group-albums', dest='group_albums', action='store_true', help='group tracks in a folder into separate albums' ) +import_cmd.parser.add_option( + '-e', '--pretend', dest='pretend', action='store_true', + help='only print files to import, but don\'t import' +) import_cmd.func = import_func default_commands.append(import_cmd) diff --git a/docs/reference/cli.rst b/docs/reference/cli.rst index fc734c492..da5f0e04a 100644 --- a/docs/reference/cli.rst +++ b/docs/reference/cli.rst @@ -48,7 +48,7 @@ import `````` :: - beet import [-CWAPRqst] [-l LOGPATH] PATH... + beet import [-CeWAPRqst] [-l LOGPATH] PATH... beet import [options] -L QUERY Add music to your library, attempting to get correct tags for it from @@ -128,6 +128,10 @@ Optional command flags: ``--group-albums`` option to split the files based on their metadata before matching them as separate albums. +* If you just want to know which files would be imported, you can use the ``-e`` + (or ``--pretend``) option. If set, beets will only print a list of file + it will import when the option is removed and won't do anything else. + .. _rarfile: https://pypi.python.org/pypi/rarfile/2.2 .. only:: html diff --git a/test/test_importer.py b/test/test_importer.py index f026adf35..468315a35 100644 --- a/test/test_importer.py +++ b/test/test_importer.py @@ -1530,6 +1530,47 @@ class ReimportTest(unittest.TestCase, ImportHelper): self.assertEqual(self._item().added, 4747.0) +class ImportPretendTest(_common.TestCase, ImportHelper): + """ Test the pretend commandline option + """ + def setUp(self): + super(ImportPretendTest, self).setUp() + self.setup_beets() + self._create_import_dir(1) + self._setup_import_session() + config['import']['pretend'] = True + self.matcher = AutotagStub().install() + self.io.install() + + def tearDown(self): + self.teardown_beets() + self.matcher.restore() + + def test_import_enumerate_only(self): + resource_path = os.path.join(_common.RSRC, u'empty.mp3') + single_path = os.path.join(self.import_dir, u'track_2.mp3') + + shutil.copy(resource_path, single_path) + import_files = [ + os.path.join(self.import_dir, u'the_album'), + single_path + ] + self._setup_import_session(singletons=True) + self.importer.paths = import_files + + self.importer.run() + out = self.io.getoutput() + + self.assertEqual(len(self.lib.items()), 0) + self.assertEqual(len(self.lib.albums()), 0) + + lines = out.splitlines() + self.assertEqual(len(lines), 2) + self.assertEqual(lines[0], os.path.join(import_files[0], + u'track_1.mp3')) + self.assertEqual(lines[1], import_files[1]) + + def suite(): return unittest.TestLoader().loadTestsFromName(__name__) From 440fe9a2ea64cca7bc99dcf297bc0d2f7d490826 Mon Sep 17 00:00:00 2001 From: Malte Ried Date: Sun, 21 Dec 2014 21:37:44 +0100 Subject: [PATCH 2/4] Reduced the count of accesses of the pretend config option. Now it's only one place where it is read. Changed the output to use the log system rather than print_. --- beets/config_default.yaml | 1 + beets/importer.py | 38 ++++++++++++++++++-------------------- beets/ui/commands.py | 2 +- test/test_importer.py | 38 ++++++++++++++++++++++++++++++-------- 4 files changed, 50 insertions(+), 29 deletions(-) diff --git a/beets/config_default.yaml b/beets/config_default.yaml index 47afe70ce..78f16d051 100644 --- a/beets/config_default.yaml +++ b/beets/config_default.yaml @@ -21,6 +21,7 @@ import: detail: no flat: no group_albums: no + pretend: false clutter: ["Thumbs.DB", ".DS_Store"] ignore: [".*", "*~", "System Volume Information"] diff --git a/beets/importer.py b/beets/importer.py index 4844fc128..a2a12158c 100644 --- a/beets/importer.py +++ b/beets/importer.py @@ -34,7 +34,6 @@ from beets import dbcore from beets import plugins from beets import util from beets import config -from beets.ui import print_ from beets.util import pipeline, sorted_walk, ancestry from beets.util import syspath, normpath, displayable_path from enum import Enum @@ -961,8 +960,6 @@ class ImportTaskFactory(object): self.toppath = toppath self.session = session self.skipped = 0 - self.pretend = session.config[ - 'pretend'] if 'pretend' in session.config else False def tasks(self): """Yield all import tasks for `self.toppath`. @@ -973,22 +970,15 @@ class ImportTaskFactory(object): for dirs, paths in self.paths(): if self.session.config['singletons']: for path in paths: - if self.pretend: - print_(displayable_path(path)) - else: - task = self.singleton(path) - if task: - yield task + task = self.singleton(path) + if task: + yield task yield self.sentinel(dirs) else: - if self.pretend: - for path in paths: - print_(displayable_path(path)) - else: - task = self.album(paths, dirs) - if task: - yield task + task = self.album(paths, dirs) + if task: + yield task def paths(self): """Walk `self.toppath` and yield pairs of directory lists and @@ -1106,18 +1096,26 @@ def read_tasks(session): task_factory = ImportTaskFactory(toppath, session) imported = False for t in task_factory.tasks(): - imported |= not t.skip - yield t + if session.config['pretend']: + imported = True + if isinstance(t, SingletonImportTask): + log.info(displayable_path(t.item['path'])) + elif t.items: + for item in t.items: + log.info(displayable_path(item['path'])) + else: + imported |= not t.skip + yield t # Indicate the directory is finished. # FIXME hack to delete extracted archives - if not task_factory.pretend: + if not session.config['pretend']: if archive_task is None: yield task_factory.sentinel() else: yield archive_task - if not imported and not task_factory.pretend: + if not imported: log.warn(u'No files imported from {0}' .format(displayable_path(user_toppath))) diff --git a/beets/ui/commands.py b/beets/ui/commands.py index 69f7cb898..c8e217d3c 100644 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -944,7 +944,7 @@ import_cmd.parser.add_option( help='group tracks in a folder into separate albums' ) import_cmd.parser.add_option( - '-e', '--pretend', dest='pretend', action='store_true', + '--pretend', dest='pretend', action='store_true', help='only print files to import, but don\'t import' ) import_cmd.func = import_func diff --git a/test/test_importer.py b/test/test_importer.py index 468315a35..d71a8def2 100644 --- a/test/test_importer.py +++ b/test/test_importer.py @@ -25,6 +25,7 @@ from mock import patch import _common from _common import unittest +from beets.util import displayable_path from helper import TestImportSession, TestHelper, has_program, capture_log from beets import importer from beets.importer import albums_in_dir @@ -1533,6 +1534,11 @@ class ReimportTest(unittest.TestCase, ImportHelper): class ImportPretendTest(_common.TestCase, ImportHelper): """ Test the pretend commandline option """ + + def __init__(self): + super(ImportPretendTest, self).__init__() + self.matcher = None + def setUp(self): super(ImportPretendTest, self).setUp() self.setup_beets() @@ -1546,7 +1552,7 @@ class ImportPretendTest(_common.TestCase, ImportHelper): self.teardown_beets() self.matcher.restore() - def test_import_enumerate_only(self): + def test_import_pretend(self): resource_path = os.path.join(_common.RSRC, u'empty.mp3') single_path = os.path.join(self.import_dir, u'track_2.mp3') @@ -1558,17 +1564,33 @@ class ImportPretendTest(_common.TestCase, ImportHelper): self._setup_import_session(singletons=True) self.importer.paths = import_files - self.importer.run() - out = self.io.getoutput() + with capture_log() as logs: + self.importer.run() self.assertEqual(len(self.lib.items()), 0) self.assertEqual(len(self.lib.albums()), 0) - lines = out.splitlines() - self.assertEqual(len(lines), 2) - self.assertEqual(lines[0], os.path.join(import_files[0], - u'track_1.mp3')) - self.assertEqual(lines[1], import_files[1]) + self.assertEqual(len(logs), 3) + self.assertEqual(logs[1], os.path.join(import_files[0], + u'track_1.mp3')) + self.assertEqual(logs[2], import_files[1]) + + def test_import_pretend_empty(self): + path = os.path.join(self.temp_dir, 'empty') + os.makedirs(path) + + self._setup_import_session(singletons=True) + self.importer.paths = [path] + + with capture_log() as logs: + self.importer.run() + + self.assertEqual(len(self.lib.items()), 0) + self.assertEqual(len(self.lib.albums()), 0) + + self.assertEqual(len(logs), 2) + self.assertEqual(logs[1], 'No files imported from {0}' + .format(displayable_path(path))) def suite(): From 5f67f3ae51fb9475975653946d7e69fc66c104cd Mon Sep 17 00:00:00 2001 From: Malte Ried Date: Sun, 21 Dec 2014 21:52:09 +0100 Subject: [PATCH 3/4] The last commit broke the tests. Repaired... --- test/test_importer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_importer.py b/test/test_importer.py index d71a8def2..0ffa9aaf3 100644 --- a/test/test_importer.py +++ b/test/test_importer.py @@ -1535,8 +1535,8 @@ class ImportPretendTest(_common.TestCase, ImportHelper): """ Test the pretend commandline option """ - def __init__(self): - super(ImportPretendTest, self).__init__() + def __init__(self, method_name='runTest'): + super(ImportPretendTest, self).__init__(method_name) self.matcher = None def setUp(self): From af36d85ef834c73ef9324dc305b7c1a651b4733d Mon Sep 17 00:00:00 2001 From: Malte Ried Date: Sun, 21 Dec 2014 21:52:09 +0100 Subject: [PATCH 4/4] Implemented a better solution for the pretend flag Corrected the documentation (shortcut -e is not available any more) --- beets/importer.py | 63 +++++++++++++++++++++++------------------- docs/reference/cli.rst | 6 ++-- 2 files changed, 38 insertions(+), 31 deletions(-) diff --git a/beets/importer.py b/beets/importer.py index a2a12158c..0877b5508 100644 --- a/beets/importer.py +++ b/beets/importer.py @@ -277,20 +277,25 @@ class ImportSession(object): else: stages = [query_tasks(self)] - if self.config['group_albums'] and \ - not self.config['singletons']: - # Split directory tasks into one task for each album - stages += [group_albums(self)] - if self.config['autotag']: - # FIXME We should also resolve duplicates when not - # autotagging. This is currently handled in `user_query` - stages += [lookup_candidates(self), user_query(self)] + if self.config['pretend']: + # Only log the imported files and end the pipeline + stages += [log_files(self)] else: - stages += [import_asis(self)] - stages += [apply_choices(self)] - for stage_func in plugins.import_stages(): - stages.append(plugin_stage(self, stage_func)) - stages += [manipulate_files(self)] + if self.config['group_albums'] and \ + not self.config['singletons']: + # Split directory tasks into one task for each album + stages += [group_albums(self)] + if self.config['autotag']: + # FIXME We should also resolve duplicates when not + # autotagging. This is currently handled in `user_query` + stages += [lookup_candidates(self), user_query(self)] + else: + stages += [import_asis(self)] + stages += [apply_choices(self)] + + for stage_func in plugins.import_stages(): + stages.append(plugin_stage(self, stage_func)) + stages += [manipulate_files(self)] pl = pipeline.Pipeline(stages) # Run the pipeline. @@ -1096,24 +1101,15 @@ def read_tasks(session): task_factory = ImportTaskFactory(toppath, session) imported = False for t in task_factory.tasks(): - if session.config['pretend']: - imported = True - if isinstance(t, SingletonImportTask): - log.info(displayable_path(t.item['path'])) - elif t.items: - for item in t.items: - log.info(displayable_path(item['path'])) - else: - imported |= not t.skip - yield t + imported |= not t.skip + yield t # Indicate the directory is finished. # FIXME hack to delete extracted archives - if not session.config['pretend']: - if archive_task is None: - yield task_factory.sentinel() - else: - yield archive_task + if archive_task is None: + yield task_factory.sentinel() + else: + yield archive_task if not imported: log.warn(u'No files imported from {0}' @@ -1299,6 +1295,17 @@ def manipulate_files(session, task): task.finalize(session) +@pipeline.stage +def log_files(session, task): + """A coroutine (pipeline stage) to log each file which will be imported + """ + if isinstance(task, SingletonImportTask): + log.info(displayable_path(task.item['path'])) + elif task.items: + for item in task.items: + log.info(displayable_path(item['path'])) + + def group_albums(session): """Group the items of a task by albumartist and album name and create a new task for each album. Yield the tasks as a multi message. diff --git a/docs/reference/cli.rst b/docs/reference/cli.rst index da5f0e04a..a11ed394e 100644 --- a/docs/reference/cli.rst +++ b/docs/reference/cli.rst @@ -48,7 +48,7 @@ import `````` :: - beet import [-CeWAPRqst] [-l LOGPATH] PATH... + beet import [-CWAPRqst] [-l LOGPATH] PATH... beet import [options] -L QUERY Add music to your library, attempting to get correct tags for it from @@ -128,8 +128,8 @@ Optional command flags: ``--group-albums`` option to split the files based on their metadata before matching them as separate albums. -* If you just want to know which files would be imported, you can use the ``-e`` - (or ``--pretend``) option. If set, beets will only print a list of file +* If you just want to know which files would be imported, you can use the + ``--pretend`` option. If set, beets will only print a list of file it will import when the option is removed and won't do anything else. .. _rarfile: https://pypi.python.org/pypi/rarfile/2.2