# -*- 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()))