Merge pull request #3900 from Lanny/master

badfiles: Optionally run checkers during import
This commit is contained in:
Adrian Sampson 2021-03-29 19:28:30 -04:00 committed by GitHub
commit eab4372a58
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 93 additions and 10 deletions

View file

@ -698,6 +698,19 @@ class TerminalImportSession(importer.ImportSession):
print_(displayable_path(task.paths, u'\n') +
u' ({0} items)'.format(len(task.items)))
# Let plugins display info or prompt the user before we go through the
# process of selecting candidate.
results = plugins.send('import_task_before_choice',
session=self, task=task)
actions = [action for action in results if action]
if len(actions) == 1:
return actions[0]
elif len(actions) > 1:
raise plugins.PluginConflictException(
u'Only one handler for `import_task_before_choice` may return '
u'an action.')
# Take immediate action if appropriate.
action = _summary_judgment(task.rec)
if action == importer.action.APPLY:

View file

@ -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('import_task_before_choice',
self.on_import_task_before_choice)
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,75 @@ 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):
if not self.config['check_on_import'].get(False):
return
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
def on_import_task_before_choice(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_()
ui.print_('What would you like to do?')
sel = ui.input_options(['aBort', 'skip', 'continue'])
if sel == 's':
return importer.action.SKIP
elif sel == 'c':
return None
elif sel == 'b':
raise importer.ImportAbort()
else:
raise Exception('Unexpected selection: {}'.format(sel))
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',

View file

@ -210,6 +210,8 @@ Other new things:
* The :doc:`/plugins/aura` has arrived!
* :doc:`plugins/fetchart`: The new ``max_filesize`` option for fetchart can be
used to target a maximum image filesize.
* :doc:`/plugins/badfiles`: Checkers can now be run during import with the
``check_on_import`` config option.
Fixes:

View file

@ -200,6 +200,13 @@ The events currently available are:
import pipeline stage is a better choice (see :ref:`plugin-stage`).
Parameters: ``task`` and ``session``.
* `import_task_before_choice`: called after candidate search for an import task
before any decision is made about how/if to import or tag. Can be used to
present information about the task or initiate interaction with the user
before importing occurs. Return an importer action to take a specific action.
Only one handler may return a non-None result.
Parameters: ``task`` and ``session``
* `import_task_choice`: called after a decision has been made about an import
task. This event can be used to initiate further interaction with the user.
Use ``task.choice_flag`` to determine or change the action to be

View file

@ -17,6 +17,7 @@ install yourself:
You can also add custom commands for a specific extension, like this::
badfiles:
check_on_import: yes
commands:
ogg: myoggchecker --opt1 --opt2
flac: flac --test --warnings-as-errors --silent
@ -25,6 +26,10 @@ Custom commands will be run once for each file of the specified type, with the
path to the file as the last argument. Commands must return a status code
greater than zero for a file to be considered corrupt.
You can run the checkers when importing files by using the `check_on_import`
option. When on, checkers will be run against every imported file and warnings
and errors will be presented when selecting a tagging option.
.. _mp3val: http://mp3val.sourceforge.net/
.. _flac: https://xiph.org/flac/