# -*- coding: utf-8 -*- # # Copyright © 2019 Nick Bowler # # License GPLv3+: GNU General Public License version 3 or any later version. # This is free software: you are free to change and redistribute it. # There is NO WARRANTY, to the extent permitted by law. PLUGIN_NAME = u"External Script Manager" PLUGIN_AUTHOR = u"Nick Bowler" PLUGIN_DESCRIPTION = u'''

Synchronizes scripts between Picard and an external directory. This is primarily intended to allow scripts to be edited using normal text editors and managed in version control.

Scripts are located under the Picard user directory, (usually ~/.config/MusicBrainz/Picard/scriptmanager. This directory will be created the first time the plugin is loaded. Files in this directory are loaded based on their extension; tagger scripts have the extension .tag.

Any scripts managed by this plugin can still be created, edited or deleted using the built-in interface within Picard. Such changes will be reflected on disk. Scripts with names that begin with //scriptmanager/ are managed by this plugin.

''' PLUGIN_VERSION = "0" PLUGIN_API_VERSIONS = ["2.0"] PLUGIN_LICENSE = "GPL-3.0-or-later" from picard import (config, log) from picard.const import (USER_DIR) from pathlib import Path from picard.ui.options.scripting import ScriptingOptionsPage import os def modulename(): return modulename.__module__[len("picard.plugins."):] # Remove at most one trailing newline from a string def read_script(file): with open(file) as f: s = f.read() if (s[-1:] == '\n'): return s[:-1] return s def write_script(file, text): try: old_text = read_script(file) except FileNotFoundError: old_text = None if (old_text != text): with open(file + ".tmp", "w") as f: f.write(text + '\n') os.replace(file + ".tmp", file) return True return False def script_files(dir = os.path.join(USER_DIR, "scriptmanager")): try: Path(dir).mkdir(exist_ok=True) except FileExistsError: log.warning("%s is not a directory" % dir) return [] tagger_scripts = {} for f in os.listdir(dir): if os.path.splitext(f)[1] == ".tag": tagger_scripts["//scriptmanager/%s" % f] = os.path.join(dir, f) return tagger_scripts # When saving scripts in the UI, write out changes to the filesystem def install_ui_save_hook(): script_save = ScriptingOptionsPage.save def save_hook(self): script_save(self) dir = os.path.join(USER_DIR, "scriptmanager") tagger_scripts = script_files(dir) for pos, name, enabled, text in config.setting["list_of_scripts"]: if name in tagger_scripts: del tagger_scripts[name] if (name.startswith("//scriptmanager/")): (basename, ext) = os.path.splitext(os.path.basename(name)) if (ext == ".tag"): file = os.path.join(dir, basename + ext) if write_script(file, text): log.info("wrote script %s" % name) else: log.warning("invalid managed script name: %s" % name) # Remove all remaining scripts for name in tagger_scripts: log.info("deleted script %s" % name) try: os.remove(tagger_scripts[name]) except FileNotFoundError: pass ScriptingOptionsPage.save = save_hook # Refresh the script list when visiting the scripting UI def install_ui_load_hook(): script_load = ScriptingOptionsPage.load def load_hook(self): refresh_scripts() script_load(self) ScriptingOptionsPage.load = load_hook # When the plugin is loaded, load scripts from the filesystem into Picard. def refresh_scripts(): tagger_scripts = script_files() # Update existing script list... list_of_scripts = config.setting["list_of_scripts"] for i, (pos, name, enabled, text) in enumerate(list_of_scripts[:]): if name in tagger_scripts: text_update = read_script(tagger_scripts[name]) if text != text_update: log.info("refreshed script %s" % name) list_of_scripts[i] = (pos, name, enabled, text_update) del tagger_scripts[name] elif name.startswith("//scriptmanager/"): log.info("removed script %s" % name) del list_of_scripts[i] # Add any new scripts that were found... for name in tagger_scripts: log.info("imported script %s" % name) text = read_script(tagger_scripts[name]) list_of_scripts.append((0, name, False, text)) # Fixup position entries and write out updated script list for i, (pos, name, enabled, text) in enumerate(list_of_scripts): list_of_scripts[i] = (i, name, enabled, text) config.setting["list_of_scripts"] = list_of_scripts if modulename() in config.setting["enabled_plugins"]: install_ui_save_hook() install_ui_load_hook() refresh_scripts() else: log.debug("%s disabled in configuration" % (modulename()))