#!/usr/bin/python3 import copy import os import sys import lxml.etree as etree from lxml.builder import ElementMaker # from lxml import etree import filing # import subscription from nitpo import Nitpo from subscription import Subscription class Profile(Nitpo): def __init__(self, do_verbose=False): """subscription profiles""" super().__init__() ns = self.const['ns'] self.nsmap = {None: ns} self.N = "{%s}" % self.const['ns'] self.E = ElementMaker(nsmap={None: self.const['ns']}) self.check_conf('folders', 'profile') self.to_print = '' self.s = Subscription() self.event = {} self.do_verbose = do_verbose #def fufi_from_emad(self, emad): # emad = emad.lower() # parts = emad.split('@') # host = parts[1] # user = parts[0] # parts = host.split('.') # parts.reverse() # folder = '' # for part in parts: # folder += '/' + part # fufi = self.conf['folders']['profile'] + folder # fufi += '/' + user + '.xml' # zufi = fufi + '.gz' # if os.path.isfile(zufi): # return zufi # return fufi def relfi_from_emad(self, emad): emad = emad.lower() parts = emad.split('@') host = parts[1] user = parts[0] parts = host.split('.') parts.reverse() folder = '' for part in parts: folder += part + '/' relfi = folder + user + '.xml' return relfi def fufi_from_emad(self, emad): relfi = self.relfi_from_emad(emad) fufi = self.conf['folders']['profile'] + '/' + relfi zufi = fufi + '.gz' if os.path.isfile(zufi): return zufi return fufi def is_dead(self, doc): """is the profile dead""" # # I have no clue as to why the namespaces don't work here xp = 'n:live/*' # print('live ' + str(doc.xpath(xp, namespaces={'n': self.const['ns']}))) live_count = len(doc.xpath(xp, namespaces={'n': self.const['ns']})) if live_count == 0: return True return False def write(self, doc, emad=None): if emad is None: emad = self.emad_from_doc(doc) # # wrong output file, to delete fufi_to_delete = None is_dead = self.is_dead(doc) out_fufi = self.fufi_from_emad(emad) filing.prepare(out_fufi) if is_dead: if not out_fufi.endswith('.gz'): out_fufi += '.gz' if os.path.isfile(out_fufi): fufi_to_delete = out_fufi[0:-3] else: if(out_fufi).endswith('.gz'): out_fufi = out_fufi[0:-3] if not out_fufi.endswith('.gz'): bad_fufi = out_fufi + '.gz' else: bad_fufi = out_fufi if os.path.isfile(bad_fufi): fufi_to_delete = bad_fufi etree.cleanup_namespaces(doc) etree.register_namespace("n", self.const['ns']) out = etree.tostring(doc, pretty_print=True).decode() has_it_been_written = filing.srite(out_fufi, out, do_verbose=self.do_verbose) if fufi_to_delete is not None: if os.path.isfile(fufi_to_delete): print(f"profile removes {fufi_to_delete}") os.remove(fufi_to_delete) return has_it_been_written def load(self, emad): fufi = self.fufi_from_emad(emad) if not os.path.isfile(fufi): return self.skel(emad) parser = etree.XMLParser(remove_blank_text=True) return etree.parse(fufi, parser) def skel(self, emad): profile_ele = self.E('profile', self.E(self.N + 'live'), self.E(self.N + 'dead'), emad=emad) doc = etree.ElementTree(profile_ele) return doc def register(self, event, do_write=True): self.event = event if 'repcode' not in event: print("profile sees an event without repcode" + str(event), file=sys.stderr) return None emad = event['emad'] doc = self.load(emad) self.to_print = etree.tostring(doc, pretty_print=True).decode() self.to_print += str(event) + '\n' if event['act'] == 'add': doc = self.register_add(doc, event) if doc is None: return None if event['act'] == 'del': doc = self.register_del(doc, event) if doc is None: return None self.to_print += etree.tostring(doc, pretty_print=True).decode() has_it_been_written = True if do_write: has_it_been_written = self.write(doc) if has_it_been_written: print(self.to_print) self.to_print = '' def register_del(self, doc, event): import dating repcode = event['repcode'] # # get the live repcode surk live_xp = '/n:profile/n:live/n:surk[@repcode="' + repcode + '"]' live_surk_eles = doc.xpath(live_xp, namespaces={'n': self.const['ns']}) if len(live_surk_eles) > 1: err = "I can't have more than 1 live surk per repcode." raise Exception(err) if len(live_surk_eles) == 1: live_surk_ele = live_surk_eles[0] # # normal case # if live_surk_ele.attrib['from'] < event['time']: if dating.is_it_a_close_date(live_surk_ele.attrib['from'], event['time']): live_surk_ele.attrib['until'] = event['time'] surk_copy = copy.deepcopy(live_surk_ele) if 'via' in event: self.sub_fields(surk_copy, 'via') # # remove from live surks live_surk_ele.getparent().remove(live_surk_ele) # # append to the dead surks xp = '/n:profile/n:dead' dead_eles = doc.xpath(xp, namespaces={'n': self.const['ns']}) if len(dead_eles) == 0: # # create dead element xp = '/n:profile' profile_ele = doc.xpath(xp, namespaces={'n': self.const['ns']})[0] dead_ele = self.E(self.N + 'dead') profile_ele.append(dead_ele) else: dead_ele = dead_eles[0] dead_ele.append(surk_copy) return doc else: print("profile can't remove a surk from before it started", file=sys.stderr) return None # # make some fixes for historic data dead_xp = '/n:profile/n:dead/n:surk[@repcode="' + repcode + '"]' dead_surk_eles = doc.xpath(dead_xp, namespaces={'n': self.const['ns']}) if len(dead_surk_eles) == 0: print('dead_xp ' + dead_xp) print("profile is asked to change a death data but there is no dead!") # file=sys.stderr) return None dead_surk_eles.reverse() # # starting with the last for dead_surk_ele in dead_surk_eles: # # in log records, we have events that are earlier than the time # # in the historic data if dead_surk_ele.attrib['until'] == event['time']: return None if dating.is_it_a_close_date(dead_surk_ele.attrib['until'], event['time']): dead_surk_ele.attrib['until'] = event['time'] self.sub_fields(dead_surk_ele, 'via') return doc # # nothing got done err = "nothing done for " + str(event) return None def register_add(self, doc, event, spro='v'): import dating repcode = event['repcode'] xp = '/n:profile/n:live/n:surk[@repcode="' + repcode + '"]' surk_eles = doc.xpath(xp, namespaces={'n': self.const['ns']}) if len(surk_eles) > 1: err = "I can't have more than 1 live surk per repcode." raise Exception(err) if len(surk_eles) == 1: do_append = False surk_ele = surk_eles[0] else: do_append = True surk_ele = etree.Element(self.N + 'surk') surk_ele.attrib['repcode'] = event['repcode'] if 'spro' in event: spro = event['spro'] surk_ele.attrib['spro'] = spro # # set time on the surk, if it's a close only # if 'from' not in surk_ele.attrib or surk_ele.attrib['from'] > event['time']: if 'from' not in surk_ele.attrib or \ dating.is_it_a_close_date(surk_ele.attrib['from'], event['time']): surk_ele.attrib['from'] = event['time'] for sub_field in ('name', 'hopa', 'via'): self.sub_fields(surk_ele, sub_field) xp = 'n:live' live_eles = doc.xpath(xp, namespaces={'n': self.const['ns']}) if len(live_eles) == 0: # # create live element xp = '/n:profile' profile_ele = doc.xpath(xp, namespaces={'n': self.const['ns']})[0] live_ele = self.E(self.N + 'live') profile_ele.append(live_ele) else: live_ele = live_eles[0] if do_append: live_ele.append(surk_ele) surk_ele = self.s.new(event) #live_ele.append(surk_ele) return doc def sub_fields(self, surk_ele, field_name): """values such as name and hopa, that are associated with a subscription""" if field_name not in self.event: return surk_ele xp = './n:' + field_name field_eles = surk_ele.xpath(xp, namespaces={'n': self.const['ns']}) if len(field_eles) > 1: raise Exception("No more that one subfield {field_name}") if len(field_eles) == 0: field_ele = etree.SubElement(surk_ele, self.N + field_name) else: field_ele = field_eles[0] field_ele.text = self.event[field_name] return surk_ele def emad_from_doc(self, doc): """should move to prodo""" xp = '@emad' emad = doc.xpath(xp, namespaces={'n': self.const['ns']})[0] return emad def last_death_time(self, doc, repcode): xp = '/n:profile/n:dead/n:surk[@repcode="' + repcode + '"]' surk_eles = doc.xpath(xp, namespaces={'n': self.const['ns']}) if len(surk_eles) == 0: return None # # assumed to be in historical order last_dead_surk_ele = surk_eles[-1] last_until = last_dead_surk_ele.attrib['until'] return last_until def repcodes(self, doc): repcodes = {} xp = '/n:profile/n:live/n:surk' surk_eles = doc.xpath(xp, namespaces={'n': self.const['ns']}) for surk_ele in surk_eles: if 'repcode' not in surk_ele.attrib: continue repcode = surk_ele.attrib['repcode'] # use a dict for fast lookup repcodes[repcode] = 1 return repcodes