2020-09-26 17:30:59 +02:00
|
|
|
import argparse
|
|
|
|
import json
|
|
|
|
|
|
|
|
from xfconf import Xfconf
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
argparser = argparse.ArgumentParser()
|
2020-09-27 16:31:13 +02:00
|
|
|
argparser.add_argument(
|
|
|
|
"--xq-path", type=str,
|
|
|
|
help="Optional path to xion-query"
|
|
|
|
)
|
|
|
|
argparser.add_argument(
|
|
|
|
"-e", "--export", type=str, nargs=3,
|
|
|
|
metavar=("CHANNEL", "ROOT", "OUTPUT"),
|
|
|
|
help=("Export settings in channel under this root. "
|
|
|
|
"Use '/' as root to export the whole channel.")
|
|
|
|
)
|
|
|
|
argparser.add_argument(
|
|
|
|
"-i", "--import", dest="import_tree", type=str,
|
|
|
|
metavar=("JSON",),
|
|
|
|
help="Import a JSON settings file"
|
|
|
|
)
|
2020-09-27 17:28:59 +02:00
|
|
|
argparser.add_argument(
|
|
|
|
"-r", "--replace", action="store_true",
|
|
|
|
help="Replace the root with imported settings, remove unknowns"
|
|
|
|
)
|
2020-09-27 16:31:13 +02:00
|
|
|
argparser.add_argument(
|
|
|
|
"-y", "--yes", action="store_true",
|
|
|
|
help="Do not ask for confirmation"
|
|
|
|
)
|
2020-09-26 17:30:59 +02:00
|
|
|
args = argparser.parse_args()
|
|
|
|
|
|
|
|
xion = Xion(xq=args.xq_path)
|
|
|
|
if args.export:
|
2020-09-27 16:31:13 +02:00
|
|
|
channel, root, output = args.export
|
2020-09-26 17:30:59 +02:00
|
|
|
tree = xion.build_tree(channel, root)
|
|
|
|
if tree is None:
|
|
|
|
print("Failed to build config tree.")
|
2020-09-27 17:28:59 +02:00
|
|
|
exit(1)
|
|
|
|
success = xion.export_tree(channel, root, tree, output)
|
|
|
|
exit(0 if success else 1)
|
2020-09-27 16:31:13 +02:00
|
|
|
elif args.import_tree:
|
|
|
|
channel, root, tree = xion.import_tree(args.import_tree)
|
|
|
|
if channel and root and tree:
|
|
|
|
force = bool(args.yes)
|
2020-09-27 17:28:59 +02:00
|
|
|
replace = bool(args.replace)
|
|
|
|
success = xion.apply_tree(channel, root, tree,
|
|
|
|
confirm=not force, replace=replace)
|
|
|
|
exit(0 if success else 1)
|
|
|
|
else:
|
|
|
|
print("Import failed.")
|
|
|
|
exit(1)
|
|
|
|
exit(0)
|
2020-09-26 17:30:59 +02:00
|
|
|
|
|
|
|
|
|
|
|
class Xion:
|
2020-09-27 17:28:59 +02:00
|
|
|
"""Manipulate Xfconf settings trees."""
|
2020-09-26 17:30:59 +02:00
|
|
|
|
|
|
|
def __init__(self, xq=None):
|
|
|
|
self.xfconf = Xfconf(xq=xq)
|
|
|
|
|
|
|
|
def build_tree(self, channel, root="/"):
|
2020-09-27 17:28:59 +02:00
|
|
|
"""Return a dict of properties in this channel, filtering on root.
|
2020-09-26 17:30:59 +02:00
|
|
|
|
2020-09-27 17:28:59 +02:00
|
|
|
Return None on error. Root has to start with "/" to be valid. Arrays are
|
|
|
|
added to the tree as a list of properties.
|
2020-09-26 17:30:59 +02:00
|
|
|
"""
|
2020-09-27 16:31:13 +02:00
|
|
|
if not root.startswith("/"):
|
|
|
|
print("Invalid root, must start with /")
|
|
|
|
return None
|
2020-09-26 17:30:59 +02:00
|
|
|
props = self.xfconf.get_property_list(channel, root=root)
|
|
|
|
if props is None:
|
|
|
|
print(f"Failed to get property list for channel {channel}.")
|
|
|
|
return None
|
|
|
|
tree = {}
|
|
|
|
for prop_name in props:
|
|
|
|
prop = self.xfconf.get_property(channel, prop_name)
|
|
|
|
if prop is None:
|
|
|
|
print(f"Failed to get property {prop_name}.")
|
|
|
|
return None
|
|
|
|
if isinstance(prop, list):
|
|
|
|
leaf = [Xion._build_prop_leaf(p) for p in prop]
|
|
|
|
else:
|
|
|
|
leaf = Xion._build_prop_leaf(prop)
|
|
|
|
tree[prop_name] = leaf
|
|
|
|
return tree
|
|
|
|
|
2020-09-27 18:13:03 +02:00
|
|
|
@staticmethod
|
|
|
|
def _build_prop_leaf(prop):
|
|
|
|
return {"type": prop.gtype, "value": str(prop.value)}
|
|
|
|
|
2020-09-27 16:31:13 +02:00
|
|
|
def export_tree(self, channel, root, tree, output_path):
|
2020-09-27 17:28:59 +02:00
|
|
|
"""Export a property tree as a sorted JSON file."""
|
2020-09-27 16:31:13 +02:00
|
|
|
tree["channel"] = channel
|
|
|
|
tree["root"] = root
|
2020-09-26 17:30:59 +02:00
|
|
|
with open(output_path, "wt") as output_file:
|
|
|
|
json.dump(tree, output_file, indent=2, sort_keys=True)
|
|
|
|
|
2020-09-27 16:31:13 +02:00
|
|
|
def import_tree(self, file_path):
|
2020-09-27 17:28:59 +02:00
|
|
|
"""Load a property tree."""
|
2020-09-27 16:31:13 +02:00
|
|
|
with open(file_path, "rt") as input_file:
|
|
|
|
tree = json.load(input_file)
|
|
|
|
try:
|
|
|
|
channel = tree.pop("channel")
|
|
|
|
root = tree.pop("root")
|
|
|
|
except KeyError:
|
|
|
|
print("Missing channel or root in JSON.")
|
|
|
|
return None, None, tree
|
|
|
|
return channel, root, tree
|
|
|
|
|
|
|
|
def apply_tree(self, channel, root, tree, confirm=True, replace=False):
|
2020-09-27 17:28:59 +02:00
|
|
|
"""Apply tree settings under root to channel, return True on success."""
|
2020-09-27 16:31:13 +02:00
|
|
|
num_changes = len(tree)
|
2020-09-27 17:28:59 +02:00
|
|
|
print(f"{num_changes} changes to do in {channel} for {root}.")
|
2020-09-27 16:31:13 +02:00
|
|
|
if replace:
|
2020-09-27 17:28:59 +02:00
|
|
|
print("This will erase all properties in the channel.")
|
2020-09-27 16:31:13 +02:00
|
|
|
if confirm and input("Confirm? [y/N]") != "y":
|
|
|
|
print("Operation cancelled.")
|
2020-09-27 17:28:59 +02:00
|
|
|
return False
|
|
|
|
if replace and not self.clear_tree(channel, root):
|
|
|
|
print("Failed to clear properties.")
|
|
|
|
return False
|
2020-09-27 16:31:13 +02:00
|
|
|
for prop, content in tree.items():
|
2020-09-27 17:28:59 +02:00
|
|
|
if not self.apply_property(channel, prop, content):
|
|
|
|
print(f"Failed to apply property {prop}.")
|
|
|
|
return False
|
|
|
|
print("Done.")
|
|
|
|
return True
|
|
|
|
|
|
|
|
def clear_tree(self, channel, root):
|
|
|
|
"""Remove all channel configs under root, return True on success."""
|
|
|
|
return self.xfconf.reset_root(channel, root)
|
2020-09-27 16:31:13 +02:00
|
|
|
|
|
|
|
def apply_property(self, channel, name, content):
|
|
|
|
"""Update one property in Xfconf, return True on success."""
|
2020-09-27 18:13:03 +02:00
|
|
|
if isinstance(content, list):
|
|
|
|
return self.xfconf.set_property_array(channel, name, content)
|
2020-09-27 16:31:13 +02:00
|
|
|
prop_type = content["type"]
|
|
|
|
value = content["value"]
|
2020-09-27 18:13:03 +02:00
|
|
|
return self.xfconf.set_property(channel, name, prop_type, value)
|
2020-09-26 17:30:59 +02:00
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
main()
|