# SPDX-License-Identifier: LGPL-2.1-or-later import gzip import json import os import sys import traceback import xml.etree.ElementTree as ET # Our own little abstraction on top of inotify. This only supports # watching directories non-recursively, but it also supports watching # directories that come and go into and out of existence. # # We could use pyinotify for this, but it would only be able to # replace the Inotify class; we would still need all the logic in the # Watcher class. class Watcher: def __init__(self): self.inotify = Inotify() self.watches = {} # path -> wd self.handlers = {} # wd -> set of callbacks def __add_watch(self, path, mask, handler): if path in self.watches: wd = self.watches[path] self.handlers[wd] = self.handlers[wd] | frozenset([handler]) else: wd = self.inotify.add_watch(path, mask) if wd >= 0: self.watches[path] = wd self.handlers[wd] = frozenset([handler]) def __rem_watch(self, path, handler): wd = self.watches[path] self.handlers[wd] = self.handlers[wd] - frozenset([handler]) if len(self.handlers[wd]) == 0: self.inotify.rem_watch(wd) del self.handlers[wd] del self.watches[path] def watch_directory(self, path, callback): events = (IN_CREATE | IN_MOVED_TO | IN_MOVED_FROM | IN_DELETE_SELF | IN_CLOSE_WRITE | IN_DELETE | IN_MOVE_SELF) def handler(mask, name): if ((mask & IN_CREATE or mask & IN_MOVED_TO) and cur_wait_name and name == cur_wait_name): reset() elif mask & (IN_DELETE_SELF | IN_MOVE_SELF): reset() elif not cur_wait_name and len(name) > 0: if mask & (IN_CLOSE_WRITE | IN_MOVED_TO | IN_DELETE | IN_MOVED_FROM): callback(os.path.join(path, name)) def reset(): self.__rem_watch(cur_path, handler) self.watch_directory(path, callback) cur_path = path cur_wait_path = None cur_wait_name = None while not os.path.exists(cur_path): cur_wait_path = cur_path cur_wait_name = os.path.basename(cur_wait_path) cur_path = os.path.dirname(cur_path) self.__add_watch(cur_path, events, handler) if cur_wait_path and os.path.exists(cur_wait_path): # Directory was created between our check and the watch setup reset() return if not cur_wait_name: for f in os.listdir(cur_path): callback(os.path.join(cur_path, f)) def run(self): def event(wd, mask, name): if wd in self.handlers: for h in self.handlers[wd]: h(mask, name) self.inotify.run(event) lang = os.environ.get('LANGUAGE') def attr_lang(elt): return elt.attrib.get('{http://www.w3.org/XML/1998/namespace}lang') def element(xml, tag): """Get an XML element from the DOM tag Retrieves the element with the matching language or by best effort - If element has the selected language (or language selection is none), return it - If the selected language can't be found, use empty language as a fallback - If no elements are found with empty language attribute, use first element found """ empty_lang = None for elt in xml.iter(tag): language = attr_lang(elt) if language == lang: return elt elif language is None and empty_lang is None: empty_lang = elt if empty_lang is not None: return empty_lang return xml.find(tag) def element_value(xml, tag): elt = element(xml, tag) return elt.text if elt is not None else None def convert_description(xml, *, use_lang=True): if xml is None: return None want_lang = lang if use_lang else None # Only the following constructs are allowed, and they all appear # at the top level: # #

text

# #
  1. text
  2. ...
# A description can have 'lang' attributes both on the actual # element and on the contained

,