1 # -*- coding: utf-8 -*-
3 # Copyright © 2019 Nick Bowler
5 # License GPLv3+: GNU General Public License version 3 or any later version.
6 # This is free software: you are free to change and redistribute it.
7 # There is NO WARRANTY, to the extent permitted by law.
9 PLUGIN_NAME = u"External Script Manager"
10 PLUGIN_AUTHOR = u"Nick Bowler"
11 PLUGIN_DESCRIPTION = u'''<p>Synchronizes scripts between Picard and an
12 external directory. This is primarily intended to allow scripts to be
13 edited using normal text editors and managed in version control.</p>
15 <p>Scripts are located under the Picard user directory, (usually
16 <code>~/.config/MusicBrainz/Picard/scriptmanager</code>. This directory
17 will be created the first time the plugin is loaded. Files in this directory
18 are loaded based on their extension; tagger scripts have the extension
19 <code>.tag</code>.</p>
21 <p>Any scripts managed by this plugin can still be created, edited or deleted
22 using the built-in interface within Picard. Such changes will be reflected on
23 disk. Scripts with names that begin with <code>//scriptmanager/</code> are
24 managed by this plugin.</p>
28 PLUGIN_API_VERSIONS = ["2.0"]
29 PLUGIN_LICENSE = "GPL-3.0-or-later"
31 from picard import (config, log)
32 from picard.const import (USER_DIR)
33 from pathlib import Path
35 from picard.ui.options.scripting import ScriptingOptionsPage
40 return modulename.__module__[len("picard.plugins."):]
42 # Remove at most one trailing newline from a string
43 def read_script(file):
50 def write_script(file, text):
52 old_text = read_script(file)
53 except FileNotFoundError:
56 if (old_text != text):
57 with open(file + ".tmp", "w") as f:
59 os.replace(file + ".tmp", file)
63 def script_files(dir = os.path.join(USER_DIR, "scriptmanager")):
65 Path(dir).mkdir(exist_ok=True)
66 except FileExistsError:
67 log.warning("%s is not a directory" % dir)
71 for f in os.listdir(dir):
72 if os.path.splitext(f)[1] == ".tag":
73 tagger_scripts["//scriptmanager/%s" % f] = os.path.join(dir, f)
76 # When saving scripts in the UI, write out changes to the filesystem
77 def install_ui_save_hook():
78 script_save = ScriptingOptionsPage.save
82 dir = os.path.join(USER_DIR, "scriptmanager")
83 tagger_scripts = script_files(dir)
84 for pos, name, enabled, text in config.setting["list_of_scripts"]:
85 if name in tagger_scripts:
86 del tagger_scripts[name]
88 if (name.startswith("//scriptmanager/")):
89 (basename, ext) = os.path.splitext(os.path.basename(name))
91 file = os.path.join(dir, basename + ext)
92 if write_script(file, text):
93 log.info("wrote script %s" % name)
95 log.warning("invalid managed script name: %s" % name)
97 # Remove all remaining scripts
98 for name in tagger_scripts:
99 log.info("deleted script %s" % name)
101 os.remove(tagger_scripts[name])
102 except FileNotFoundError:
104 ScriptingOptionsPage.save = save_hook
106 # Refresh the script list when visiting the scripting UI
107 def install_ui_load_hook():
108 script_load = ScriptingOptionsPage.load
112 ScriptingOptionsPage.load = load_hook
114 # When the plugin is loaded, load scripts from the filesystem into Picard.
115 def refresh_scripts():
116 tagger_scripts = script_files()
118 # Update existing script list...
119 list_of_scripts = config.setting["list_of_scripts"]
120 for i, (pos, name, enabled, text) in enumerate(list_of_scripts[:]):
121 if name in tagger_scripts:
122 text_update = read_script(tagger_scripts[name])
123 if text != text_update:
124 log.info("refreshed script %s" % name)
125 list_of_scripts[i] = (pos, name, enabled, text_update)
126 del tagger_scripts[name]
127 elif name.startswith("//scriptmanager/"):
128 log.info("removed script %s" % name)
129 del list_of_scripts[i]
131 # Add any new scripts that were found...
132 for name in tagger_scripts:
133 log.info("imported script %s" % name)
134 text = read_script(tagger_scripts[name])
135 list_of_scripts.append((0, name, False, text))
137 # Fixup position entries and write out updated script list
138 for i, (pos, name, enabled, text) in enumerate(list_of_scripts):
139 list_of_scripts[i] = (i, name, enabled, text)
140 config.setting["list_of_scripts"] = list_of_scripts
142 if modulename() in config.setting["enabled_plugins"]:
143 install_ui_save_hook()
144 install_ui_load_hook()
147 log.debug("%s disabled in configuration" % (modulename()))