]> git.draconx.ca Git - picard-plugins.git/blob - external-script-manager.py
tweak-filename-filter: Update for picard 2.3.1
[picard-plugins.git] / external-script-manager.py
1 # -*- coding: utf-8 -*-
2 #
3 # Copyright © 2019 Nick Bowler
4 #
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.
8
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>
14
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>
20
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>
25 '''
26
27 PLUGIN_VERSION = "0"
28 PLUGIN_API_VERSIONS = ["2.0"]
29 PLUGIN_LICENSE = "GPL-3.0-or-later"
30
31 from picard import (config, log)
32 from picard.const import (USER_DIR)
33 from pathlib import Path
34
35 from picard.ui.options.scripting import ScriptingOptionsPage
36
37 import os
38
39 def modulename():
40     return modulename.__module__[len("picard.plugins."):]
41
42 # Remove at most one trailing newline from a string
43 def read_script(file):
44     with open(file) as f:
45         s = f.read()
46     if (s[-1:] == '\n'):
47         return s[:-1]
48     return s
49
50 def write_script(file, text):
51     try:
52         old_text = read_script(file)
53     except FileNotFoundError:
54         old_text = None
55
56     if (old_text != text):
57         with open(file + ".tmp", "w") as f:
58             f.write(text + '\n')
59         os.replace(file + ".tmp", file)
60         return True
61     return False
62
63 def script_files(dir = os.path.join(USER_DIR, "scriptmanager")):
64     try:
65         Path(dir).mkdir(exist_ok=True)
66     except FileExistsError:
67         log.warning("%s is not a directory" % dir)
68         return []
69
70     tagger_scripts = {}
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)
74     return tagger_scripts
75
76 # When saving scripts in the UI, write out changes to the filesystem
77 def install_ui_save_hook():
78     script_save = ScriptingOptionsPage.save
79     def save_hook(self):
80         script_save(self)
81
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]
87
88             if (name.startswith("//scriptmanager/")):
89                 (basename, ext) = os.path.splitext(os.path.basename(name))
90                 if (ext == ".tag"):
91                     file = os.path.join(dir, basename + ext)
92                     if write_script(file, text):
93                         log.info("wrote script %s" % name)
94                 else:
95                     log.warning("invalid managed script name: %s" % name)
96
97         # Remove all remaining scripts
98         for name in tagger_scripts:
99             log.info("deleted script %s" % name)
100             try:
101                 os.remove(tagger_scripts[name])
102             except FileNotFoundError:
103                 pass
104     ScriptingOptionsPage.save = save_hook
105
106 # Refresh the script list when visiting the scripting UI
107 def install_ui_load_hook():
108     script_load = ScriptingOptionsPage.load
109     def load_hook(self):
110         refresh_scripts()
111         script_load(self)
112     ScriptingOptionsPage.load = load_hook
113
114 # When the plugin is loaded, load scripts from the filesystem into Picard.
115 def refresh_scripts():
116     tagger_scripts = script_files()
117
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]
130
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))
136
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
141
142 if modulename() in config.setting["enabled_plugins"]:
143     install_ui_save_hook()
144     install_ui_load_hook()
145     refresh_scripts()
146 else:
147     log.debug("%s disabled in configuration" % (modulename()))