diff --git a/beets/importer.py b/beets/importer.py index 458039482..e8e28b3bd 100644 --- a/beets/importer.py +++ b/beets/importer.py @@ -949,20 +949,52 @@ class ArchiveImportTask(SentinelImportTask): class ImportTaskFactory(object): - """Create album and singleton import tasks from paths for toppaths - in session. + """Create album and singleton import tasks for all media files in a + directory or path. - The `singleton()` and `album()` methods accept paths and return - instances of `SingletonImportTask` and `ImportTask`, respectively. - `None` is returned if either no media file items could be created - from the paths or if the paths have already been imported. In both - cases it logs messages. + Depending on the session's 'flat' and 'singleton' configuration, it + groups all media files contained in `toppath` into singleton or + album import tasks. """ def __init__(self, toppath, session): self.toppath = toppath self.session = session self.skipped = 0 + def tasks(self): + """Yield all import tasks for `self.toppath`. + + The behavior is configured by the session's 'flat', and + 'singleton' flags. + """ + for dirs, paths in self.paths(): + if self.session.config['singletons']: + for path in paths: + task = self.singleton(path) + if task: + yield task + yield self.sentinel(dirs) + + else: + task = self.album(paths, dirs) + if task: + yield task + + def paths(self): + """Walk `self.toppath` and yield pairs of directory lists and + path lists. + """ + if not os.path.isdir(syspath(self.toppath)): + yield ([self.toppath], [self.toppath]) + elif self.session.config['flat']: + paths = [] + for dirs, paths_in_dir in albums_in_dir(self.toppath): + paths += paths_in_dir + yield ([self.toppath], paths) + else: + for dirs, paths in albums_in_dir(self.toppath): + yield (dirs, paths) + def singleton(self, path): if self.session.already_imported(self.toppath, [path]): log.debug(u'Skipping previously-imported path: {0}' @@ -976,17 +1008,16 @@ class ImportTaskFactory(object): else: return None - def album(self, paths, dir=None): + def album(self, paths, dirs=None): """Return `ImportTask` with all media files from paths. - `dir` is a common parent directory of all paths. + `dirs` is a list of parent directories used to record already + imported albums. """ if not paths: return None - if dir: - dirs = [dir] - else: + if dirs is None: dirs = list(set(os.path.dirname(p) for p in paths)) if self.session.already_imported(self.toppath, dirs): @@ -1038,10 +1069,9 @@ def read_tasks(session): """ skipped = 0 for toppath in session.paths: - task_factory = ImportTaskFactory(toppath, session) - # Determine if we want to resume import of the toppath session.ask_resume(toppath) + user_toppath = toppath # Extract archives. archive_task = None @@ -1063,42 +1093,11 @@ def read_tasks(session): # Continue reading albums from the extracted directory. toppath = archive_task.toppath - # Check whether the path is to a file. - if not os.path.isdir(syspath(toppath)): - if session.config['singletons']: - task = task_factory.singleton(toppath) - else: - task = task_factory.album([toppath], dir=toppath) - - if task: - yield task - yield task_factory.sentinel() - continue - - # A flat album import merges all items into one album. - if session.config['flat'] and not session.config['singletons']: - paths = [] - for _, item_paths in albums_in_dir(toppath): - paths += item_paths - task = task_factory.album(paths) - if task: - yield task - yield task_factory.sentinel() - continue - - # Produce paths under this directory. - for dirs, paths in albums_in_dir(toppath): - if session.config['singletons']: - for path in paths: - task = task_factory.singleton(path) - if task: - yield task - yield task_factory.sentinel(dirs) - - else: - task = task_factory.album(paths) - if task: - yield task + task_factory = ImportTaskFactory(toppath, session) + imported = False + for t in task_factory.tasks(): + imported |= not t.skip + yield t # Indicate the directory is finished. # FIXME hack to delete extracted archives @@ -1107,6 +1106,10 @@ def read_tasks(session): else: yield archive_task + if not imported: + log.warn(u'No files imported from {0}' + .format(displayable_path(user_toppath))) + # Show skipped directories. if skipped: log.info(u'Skipped {0} directories.'.format(skipped)) diff --git a/test/test_importer.py b/test/test_importer.py index 18f16254a..f026adf35 100644 --- a/test/test_importer.py +++ b/test/test_importer.py @@ -25,7 +25,7 @@ from mock import patch import _common from _common import unittest -from helper import TestImportSession, TestHelper, has_program +from helper import TestImportSession, TestHelper, has_program, capture_log from beets import importer from beets.importer import albums_in_dir from beets.mediafile import MediaFile @@ -600,6 +600,24 @@ class ImportTest(_common.TestCase, ImportHelper): self.importer.run() self.assertEqual(len(self.lib.items()), 1) + def test_empty_directory_warning(self): + import_dir = os.path.join(self.temp_dir, 'empty') + self.touch('non-audio', dir=import_dir) + self._setup_import_session(import_dir=import_dir) + with capture_log() as logs: + self.importer.run() + + self.assertIn('No files imported from {0}'.format(import_dir), logs) + + def test_empty_directory_singleton_warning(self): + import_dir = os.path.join(self.temp_dir, 'empty') + self.touch('non-audio', dir=import_dir) + self._setup_import_session(import_dir=import_dir, singletons=True) + with capture_log() as logs: + self.importer.run() + + self.assertIn('No files imported from {0}'.format(import_dir), logs) + class ImportTracksTest(_common.TestCase, ImportHelper): """Test TRACKS and APPLY choice.