#!/usr/bin/python3 import copy import os import sys import lxml.etree as etree from lxml.builder import ElementMaker from lxml import etree import dating import filer 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.s = Subscription() self.N = "{%s}" % self.const['ns'] self.E = ElementMaker(nsmap={None: self.const['ns']}) self.check_conf('folders', 'profile') 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 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) #if is_dead: # print("It is dead.") out_fufi = self.fufi_from_emad(emad) filer.prepare(out_fufi) if is_dead: # print("It 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() filer.srite(out_fufi, out, do_verbose=True) 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) def feed(self, event): self.event = event if 'emad' not in event: raise Exception("I need an emad in " + str(event)) self.load(event['emad']) 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): if event['act'] == 'add': doc = self.register_add(event) if doc is None: return None if event['act'] == 'del': doc = self.register_del(event) if doc is None: return None print(etree.tostring(doc, pretty_print=True).decode()) self.write(doc) def register_del(self, event): self.event = event emad = event['emad'] doc = self.load(emad) print(etree.tostring(doc, pretty_print=True).decode(), end='') print(event) emad = event['emad'] if 'repcode' not in event: print("profile sees an event without repcode" + str(event), file=sys.stderr) return None 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) # # 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'] return doc # # nothing got done err = "nothing done for " + str(event) return None 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 register_add(self, event, spro='v'): self.event = event emad = event['emad'] doc = self.load(emad) print(etree.tostring(doc, pretty_print=True).decode(), end='') print(event) if 'repcode' not in event: print("profile sees an event without repcode" + str(event), file=sys.stderr) return None repcode = event['repcode'] # # check if we try to add something before the last death datetime # # this check is only needed for historical data #last_until = self.last_death_time(doc, repcode) #if last_until is not None and event['time'] < last_until: # print('time ' + event['time']) # print('last_until ' + last_until) # self.insert_historic_from(doc, event) # return doc 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): xp = '@emad' emad = doc.xpath(xp, namespaces={'n': self.const['ns']})[0] return emad