From e96b5a7eba2bb7a76ada3b18fe93ae8cac046852 Mon Sep 17 00:00:00 2001 From: Ryan Lanny Jenkins Date: Tue, 23 Mar 2021 21:55:51 -0500 Subject: [PATCH] Check files during import, show checker warnings and errors along with tagging choices. --- beets/importer.py | 1 + beets/ui/commands.py | 23 ++++++++++------- beetsplug/badfiles.py | 60 +++++++++++++++++++++++++++++++++++-------- 3 files changed, 65 insertions(+), 19 deletions(-) diff --git a/beets/importer.py b/beets/importer.py index 3220b260f..c16350c68 100644 --- a/beets/importer.py +++ b/beets/importer.py @@ -472,6 +472,7 @@ class ImportTask(BaseImportTask): def __init__(self, toppath, paths, items): super(ImportTask, self).__init__(toppath, paths, items) self.choice_flag = None + self.skip_summary_judgement = False self.cur_album = None self.cur_artist = None self.candidates = [] diff --git a/beets/ui/commands.py b/beets/ui/commands.py index 4d010f4b1..8d169129a 100755 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -698,14 +698,18 @@ class TerminalImportSession(importer.ImportSession): print_(displayable_path(task.paths, u'\n') + u' ({0} items)'.format(len(task.items))) - # Take immediate action if appropriate. - action = _summary_judgment(task.rec) - if action == importer.action.APPLY: - match = task.candidates[0] - show_change(task.cur_artist, task.cur_album, match) - return match - elif action is not None: - return action + # Call here so plugins have the option to skip `_summary_judgement` + choices = self._get_choices(task) + + if not task.skip_summary_judgement: + # Take immediate action if appropriate. + action = _summary_judgment(task.rec) + if action == importer.action.APPLY: + match = task.candidates[0] + show_change(task.cur_artist, task.cur_album, match) + return match + elif action is not None: + return action # Loop until we have a choice. while True: @@ -713,7 +717,6 @@ class TerminalImportSession(importer.ImportSession): # `choose_candidate` may be an `importer.action`, an # `AlbumMatch` object for a specific selection, or a # `PromptChoice`. - choices = self._get_choices(task) choice = choose_candidate( task.candidates, False, task.rec, task.cur_artist, task.cur_album, itemcount=len(task.items), choices=choices @@ -742,6 +745,8 @@ class TerminalImportSession(importer.ImportSession): assert isinstance(choice, autotag.AlbumMatch) return choice + choices = self._get_choices(task) + def choose_item(self, task): """Ask the user for a choice about tagging a single item. Returns either an action constant or a TrackMatch object. diff --git a/beetsplug/badfiles.py b/beetsplug/badfiles.py index 36b45de3a..c54190eaf 100644 --- a/beetsplug/badfiles.py +++ b/beetsplug/badfiles.py @@ -30,6 +30,7 @@ from beets.plugins import BeetsPlugin from beets.ui import Subcommand from beets.util import displayable_path, par_map from beets import ui +from beets import importer class CheckerCommandException(Exception): @@ -54,6 +55,11 @@ class BadFiles(BeetsPlugin): super(BadFiles, self).__init__() self.verbose = False + self.register_listener('import_task_start', + self.on_import_task_start) + self.register_listener('before_choose_candidate', + self.on_before_choose_candidate) + def run_command(self, cmd): self._log.debug(u"running command: {}", displayable_path(list2cmdline(cmd))) @@ -115,7 +121,7 @@ class BadFiles(BeetsPlugin): if not checker: self._log.error(u"no checker specified in the config for {}", ext) - return + return [] path = item.path if not isinstance(path, six.text_type): path = item.path.decode(sys.getfilesystemencoding()) @@ -130,25 +136,59 @@ class BadFiles(BeetsPlugin): ) else: self._log.error(u"error invoking {}: {}", e.checker, e.msg) - return + return [] + + error_lines = [] + if status > 0: - ui.print_(u"{}: checker exited with status {}" - .format(ui.colorize('text_error', dpath), status)) + error_lines.append(u"{}: checker exited with status {}" + .format(ui.colorize('text_error', dpath), status)) for line in output: - ui.print_(u" {}".format(line)) + error_lines.append(u" {}".format(line)) + elif errors > 0: - ui.print_(u"{}: checker found {} errors or warnings" - .format(ui.colorize('text_warning', dpath), errors)) + error_lines.append(u"{}: checker found {} errors or warnings" + .format(ui.colorize('text_warning', dpath), errors)) for line in output: - ui.print_(u" {}".format(line)) + error_lines.append(u" {}".format(line)) elif self.verbose: - ui.print_(u"{}: ok".format(ui.colorize('text_success', dpath))) + error_lines.append(u"{}: ok".format(ui.colorize('text_success', dpath))) + + return error_lines + + def on_import_task_start(self, task, session): + checks_failed = [] + + for item in task.items: + error_lines = self.check_item(item) + if error_lines: + checks_failed.append(error_lines) + + if checks_failed: + task._badfiles_checks_failed = checks_failed + task.skip_summary_judgement = True + + def on_before_choose_candidate(self, task, session): + if hasattr(task, '_badfiles_checks_failed'): + ui.print_('{} one or more files failed checks:' + .format(ui.colorize('text_warning', 'BAD'))) + for error in task._badfiles_checks_failed: + for error_line in error: + ui.print_(error_line) + + ui.print_() + def command(self, lib, opts, args): # Get items from arguments items = lib.items(ui.decargs(args)) self.verbose = opts.verbose - par_map(self.check_item, items) + + def check_and_print(item): + for error_line in self.check_item(item): + ui.print_(error_line) + + par_map(check_and_print, items) def commands(self): bad_command = Subcommand('bad',