]> git.draconx.ca Git - picard-plugins.git/commitdiff
Add plugin for managing picard tagger scripts as files.
authorNick Bowler <nbowler@draconx.ca>
Tue, 23 Jul 2019 06:00:34 +0000 (02:00 -0400)
committerNick Bowler <nbowler@draconx.ca>
Tue, 23 Jul 2019 06:00:34 +0000 (02:00 -0400)
external-script-manager.py [new file with mode: 0644]

diff --git a/external-script-manager.py b/external-script-manager.py
new file mode 100644 (file)
index 0000000..f3adcd7
--- /dev/null
@@ -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'''<p>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.</p>
+
+<p>Scripts are located under the Picard user directory, (usually
+<code>~/.config/MusicBrainz/Picard/scriptmanager</code>.  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
+<code>.tag</code>.</p>
+
+<p>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 <code>//scriptmanager/</code> are
+managed by this plugin.</p>
+'''
+
+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()))