diff --git a/picard/album.py b/picard/album.py index 715e01f464..a3c801f2d7 100644 --- a/picard/album.py +++ b/picard/album.py @@ -753,3 +753,20 @@ def register_album_post_removal_processor(function, priority=PluginPriority.NORM def run_album_post_removal_processors(album_object): _album_post_removal_processors.run(album_object) + +_album_post_save_processors = PluginFunctions(label='album_post_save_processors') + + +def register_album_post_save_processor(function, priority=PluginPriority.NORMAL): + """Registers an album-removed processor. + Args: + function: function to call after album save, it will be passed the album object + priority: optional, PluginPriority.NORMAL by default + Returns: + None + """ + _album_post_save_processors.register(function.__module__, function, priority) + + +def run_album_post_save_processors(album_object): + _album_post_save_processors.run(album_object) diff --git a/picard/tagger.py b/picard/tagger.py index 4d9d5868ff..1faeb2610d 100644 --- a/picard/tagger.py +++ b/picard/tagger.py @@ -76,6 +76,7 @@ Album, NatAlbum, run_album_post_removal_processors, + run_album_post_save_processors, ) from picard.browser.browser import BrowserIntegration from picard.browser.filelookup import FileLookup @@ -98,7 +99,10 @@ ) from picard.dataobj import DataObject from picard.disc import Disc -from picard.file import File +from picard.file import ( + File, + register_file_post_save_processor, +) from picard.formats import open_ as open_file from picard.i18n import setup_gettext from picard.pluginmanager import PluginManager @@ -294,6 +298,10 @@ def __init__(self, picard_args, unparsed_args, localedir, autoupdate): if self.autoupdate_enabled: self.updatecheckmanager = UpdateCheckManager(parent=self.window) + # track all file save completions for album save + self._album_saves = [] + register_file_post_save_processor(self._file_post_save) + def register_cleanup(self, func): self.exit_cleanup.append(func) @@ -658,9 +666,14 @@ def get_files_from_objects(self, objects, save=False): def save(self, objects): """Save the specified objects.""" - files = self.get_files_from_objects(objects, save=True) - for file in files: - file.save() + for o in objects: + log.debug('save object %r', o) + files = self.get_files_from_objects([o, ], save=True) + for file in files: + file.save() + if isinstance(o, Album): + # track the album and its files pending save + self._album_saves.append((o, files)) def load_album(self, album_id, discid=None): album_id = self.mbid_redirects.get(album_id, album_id) @@ -773,6 +786,17 @@ def remove(self, objects): if files: self.remove_files(files) + # run album_save hooks when the last file is saved + def _file_post_save(self, file): + for album, files in self._album_saves: + if file in files: + files.remove(file) + if not files: + log.debug("Album %r saved, running post_save_processors", album) + run_album_post_save_processors(album) + self._album_saves.remove((album, files)) + return + def _lookup_disc(self, disc, result=None, error=None): self.restore_cursor() if error is not None: