From 9d3c4937f88f5e236bf45827af69b0b76224049d Mon Sep 17 00:00:00 2001 From: Nick Bowler Date: Tue, 23 Jul 2019 02:00:34 -0400 Subject: [PATCH] Add plugin for managing picard tagger scripts as files. --- external-script-manager.py | 147 +++++++++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 external-script-manager.py diff --git a/external-script-manager.py b/external-script-manager.py new file mode 100644 index 0000000..f3adcd7 --- /dev/null +++ b/external-script-manager.py @@ -0,0 +1,147 @@ +# -*- 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())) -- 2.43.2