#!/usr/bin/python3 import copy import os import sys import lxml.etree as etree from lxml.builder import ElementMaker from datetime import datetime import docing import filing 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'): fufi_to_delete = out_fufi out_fufi += '.gz' # if os.path.isfile(out_fufi): # fufi_to_delete = out_fufi[0:-3] else: if(out_fufi).endswith('.gz'): fufi_to_delete = out_fufi 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 delete_report(self, emad, repcode, do_write=True): """removle a report beacuse it is closed""" doc = self.load(emad) # # 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) == 0: return None live_surk_ele = live_surk_eles[0] surk_copy = copy.deepcopy(live_surk_ele) surk_copy.attrib['end'] = datetime.today().strftime('%Y-%m-%d') 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) if do_write: has_it_been_written = self.write(doc) else: print(docing.show(doc)) quit() 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