Plugin event handling

I have been using the hook plugin to run operations of files when changed or first imported. For changed files I use the after_write event. That event is fired multiple times during an import, so the operation is repeated. I am considering writing a plugin to do it instead, but the plugin would need to know if an import operation is currently in progress.
Is there a function or variable plugins can gain access to that will tell me whether an import is in progress? This way I can tell my plugin to ignore the write event during imports.

Yo—I don’t think that is feasible (it would require too much global, mutable state). Maybe a good way to do it would be to only trigger the action if the file is actually in the library directory?

(Or maybe more details on what you’re doing would help shake out other ideas?)

That method might reduce the multiple writes, but there must at least be one call in the library directory and then the event for a finished import is fired. This would still have at least 2 runs which is better.
I am writing a plugin to keep a par set for each song and update it when the file changes.

In a related vein, if I only call item.store(), will beets keep the changes and write them to disk at the end? If so, will that only happen on import?

The only other idea I had was to add a cached writing system for all plugins. Any time a plugin calls item.write() beets could hold the operation in queue until the end of the import and essentially make one write and thus fire one write event. I’m sure that would be a lot of work though.

I will give your idea a try and see how many write events fire during an import.

store() only ever affects the database. The importer has a stage (at the end) that writes updates from the database to the files. You can also do write() to do that immediately, but you wouldn’t want to do that in an import stage.

Well-behaved plugins already sort of do a version of what you’re proposing. They just do store() and rely on the importer pipeline to write() just once at the very end. If a plugin doesn’t do that, it should probably be fixed.

I ran an import without my new par2 plugin with verbose. after_write is fired twice for each file (once after the file is moved and once for embedart plugin. Then, the album_imported event is fired and my script runs again.
How can I narrow those down even with a plugin without knowing if an import is happening?
The only thing I seem to be able to do right now is only listen for the after_write event to only have my script run twice for every file instead of 3 times.

Hard to say exactly—maybe try disabling plugins and enabling them one at a time?

In my previous message I mentioned the write is coming from the embedart plugin.

Are there any exposed methods or properties that can be combined logically to determine that an import may be happening? Through ui, or session, or task perhaps?
What is the easiest way to determine what methods and properties a plugin has access to (like a whole list for ui, session, task, etc)?

Oops, sorry, missed that! Yeah, makes sense that embedart would need to write files. :slight_smile:

I don’t think there’s a good way to get a notion of where an event is “coming from,” nor whether or not an import is currently active. I think this is unlikely to work because “is an import currently happening” is not represented in global, mutable state—it’s just like any other function you might call in beets.

As far as what is available to a plugin, the place to look is the source code—for example, look at the list of methods on ImportTask, for example:

I’ve got a crazy idea. What if I disable the listener for after_write in the function for the import_started or whatever event. Then, at the end of the function for album_imported event I set the listener for after_write again?
Can you think of any issue that might arise from this?
Would it even work like that?
Can I even unregister a listener?

Sure! You need not even unregister your listener—you could just set a Boolean flag in your own plugin, and check that flag when you get the after_write event (and do nothing if it’s true).

This will probably work, but it will run into issues if there are ever two things happening concurrently in beets. Probably nothing to worry about.

That’s good. I thought about doing that, but I was convinced that a new instance of my plugin class would be created each time one of the listeners was called.
Thank you, I will try this.

I have another question. Is it safe to change directory in my plugn? Will it mess up operations from other plugins that come after mine?

I don’t know—most things in beets use absolute paths, so maybe it would be fine? But if I were you, I’d avoid it if possible because there’s so much other code running that it’s usually a good idea not to change global, mutable state (like the cwd).

I was able to get it working without changing the working directory.
I am running on macOS and pydoc is not finding beets.ui. I installed from git with python3 setup.py install. Did I need some options to make it install the docs? How do I get them?

That’s mysterious. My understanding of pydoc is that it just uses Python modules themselves and looks up their docstrings—there should be nothing else to install, so long as you can import a given module successfully.

OK, got pydoc working.

What is the correct way to send an irrecoverable error up so that the plugin does not load?

Setting a variable to prevent the after_write event funtion from running did not work. It must create a new instance of the plugin class each time a trigger is called like I thought. Any other ideas?
Would it be possible to add a function to de-register a listener?

Hmm; it definitely doesn’t do that… it’s the same plugin instance across events. Maybe something else went wrong?

I erroneously tried to add an import stage, but that makes the par2 files in the source directory. I am going to try it again soon to be sure I got it right.

Also, I would like to check for existence of an executable file in the init function. If the file is not found I want it to raise a configuration error, preventing the plugin from loading. How can I do this?