Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add hook plugin (fixes #1561) #1603

Merged
merged 26 commits into from
Apr 30, 2016
Merged

Conversation

jackwilsdon
Copy link
Member

Adds a hook plugin that allows commands to be run when a beets event is emitted, as well as tests and documentation for the plugin. This PR fixes #1561.

There's some sections of this code that I'm not all too comfortable with, mainly how hook_function implements STDOUT redirection (feels a bit hacky, however I can't see a better way at the moment), as well as how hook_function handles stringifying arguments (will this fail under different encodings?).

Also, is there a better way of doing what I'm doing here? I'm having to call load_plugin after generating the configuration, which just feels wrong to me. This is due to how the plugin itself is coded, as all of the hooks are created inside __init__, which is called when load_plugin executes (so I can't put the load_plugin call inside the setUp method). Would moving the initialisation code to a listener for pluginload fix this? (although then we'd need to call all pluginload hooks in the configuration manually, as we're only adding them after pluginload has already been emitted by beets)

Looking for feedback on the overall code quality.

This plugin allows users to execute scripts on
different events, as well as forward any
arguments from the events to the script.
Iterate substitute_args instead of kwargs, as we
ignore anything that is not in substitute_args
already.

Fix an issue where a hook argument containing
non-ascii characters caused an exception.
@sampsyo
Copy link
Member

sampsyo commented Sep 11, 2015

Cool! It looks like there's a lot In common with @m-urban's runafter plugin in #1561. Perhaps we should join forces between the two?


# TODO: Find a better way of converting arguments to strings, as I
# currently have a feeling that forcing everything to utf-8 might
# end up causing a mess.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed; we'll need to encode the strings as bytes using the platform's preferred encoding. That's the same encoding we use for decoding arguments, from the _arg_encoding function.

@jackwilsdon
Copy link
Member Author

Oops! Somehow didn't see runafter, but I agree it would be a great idea to merge the functionality.

@m-urban
Copy link
Contributor

m-urban commented Sep 12, 2015

No worries, feel free to take over whatever you possibly need from runafter. I am a little busy right now anyways… should have a little more room for code contributions after next week again.

if key in kwargs:
hook_command = hook_command.replace(substitute_args[key],
unicode(kwargs[key],
_arg_encoding()))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tiny detail: I usually think s.decode(encoding) is more logical than unicode(s, encoding).

@sampsyo
Copy link
Member

sampsyo commented Nov 7, 2015

Sorry for falling down on this review here! This looks like it's in great shape—nearly ready to merge.

@l-t-k
Copy link
Contributor

l-t-k commented Dec 3, 2015

Really like this plugin! Is there a way to use the album path on event album_imported? Did some tests, but they failed. Not sure if this is the right place to discuss this.

Tried with:
- event: album_imported
command: echo "writing to \"%FILE_NAME%\""
substitute_args:
album_path: %FILE_NAME%

And:
- event: album_imported
command: echo "writing to \"%FILE_NAME%\""
substitute_args:
album.path: %FILE_NAME%

Both end up with: writing to "%FILE_NAME%"

 - Remove `shell` option and split all commands using `shlex.split`
   before passing them to `subprocess.Popen`.
 - General refactor of hook plugin code - move hook creation function
   inside `HookPlugin`.
 - Add improved error handling for invalid (i.e. empty) commands or
   commands that do not exist.
 - Fix `test_event_X` name collision between tests causing tests to
   fail unexpectedly.
 - Update tests to match new hook plugin design (i.e. remove shell and
   subtitution option testing).
@jackwilsdon
Copy link
Member Author

jackwilsdon commented Apr 18, 2016

@kooimens, you should be able to do this now (since 8b4f349):

hook:
  hooks:
     - event: album_imported
       command: echo "writing to \"{album.path}\""

@sampsyo
Copy link
Member

sampsyo commented Apr 18, 2016

Good point about exception handling. I think the right thing to do here may be to just print an error message and move on. That is, beets should complain loudly but not actually stop execution if the command execution fails, whether it's because the command can't be found or because it returns a nonzero exit status.

That would look something like this:

try:
    subprocess.Popen(...)
except OSError as exc:
    self._log.error("hook for {} failed: {}", event, exc)

In general, I use these rules for deciding how to handle errors:

  • Serious, unrecoverable errors that should be somewhat user-intelligible stop execution with a UserError.
  • Less serious errors that nonetheless require user interaction log an error message and move on.
  • Only completely unexpected problems, which probably indicate a bug, are allowed to show a stacktrace.

@jackwilsdon
Copy link
Member Author

Sounds good, I'll update the code to reflect that.

@codecov-io
Copy link

Current coverage is 76.86%

Merging #1603 into master will increase coverage by +<.01%

@@             master      #1603   diff @@
==========================================
  Files            62         63     +1   
  Lines         12900      12930    +30   
  Methods           0          0          
  Messages          0          0          
  Branches          0          0          
==========================================
+ Hits           9912       9938    +26   
- Misses         2988       2992     +4   
  Partials          0          0          
  1. 2 files (not in diff) in beets/util were modified. more

Powered by Codecov. Last updated by 2a5f9be

@sampsyo sampsyo merged commit 7c9440c into beetbox:master Apr 30, 2016
sampsyo added a commit that referenced this pull request Apr 30, 2016
@sampsyo
Copy link
Member

sampsyo commented Apr 30, 2016

Thank you for seeing this through to the conclusion! I'm really happy with how it turned out. I just merged the new plugin with a few rewordings in the docs.

@jackwilsdon
Copy link
Member Author

jackwilsdon commented Apr 30, 2016

Lovely, thanks! 😄

@m-urban
Copy link
Contributor

m-urban commented May 1, 2016

This is awesome. Great job, @jackwilsdon!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Let beets execute a shell script after import/update
5 participants