import copy import os import sys import lxml.etree as etree from datetime import datetime import docing import filing from lxml.builder import ElementMaker from nitpo import Nitpo from surks import Surks class Profile(Nitpo): def __init__(self, do_verbose=False): """subber 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 = Surks() self.event = {} self.do_verbose = do_verbose 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 + '/' # for p.o'brien --> p.o’brien if "'" in user: user = user.replace("'", "’") relfi = folder + user + '.xml' return relfi def base_from_fufi(self, fufi): base = fufi.replace(self.conf['folders']['profile'] + '/', '') if base.endswith('.xml'): base = base[:-4] return base 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:profile/n:live/n:surk' # 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 # bounced email xp = '/n:profile/@bounced' bounced = doc.xpath(xp, namespaces={'n': self.const['ns']}) if len(bounced) > 0: return True return False def urdate(self, doc): """is the profile dead""" # # I have no clue as to why the namespaces don't work here xp = '/n:profile/*/n:surk/@from' froms = doc.xpath(xp, namespaces={'n': self.const['ns']}) if len(froms) == 0: show = docing.show(doc) print(f"no urdate in \n\n{show}", file = sys.stderr) return None # bounced email urdate = datetime.today().strftime('%Y-%m-%d') for date in froms: # # in case there are times of day date = date[0:10] if date < urdate: urdate = date return urdate 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 delete_report(self, emad, repcode, dont_write=False): """remove a report because it is closed""" doc = self.load(emad) if dont_write: print(docing.show(doc)) # # 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 not dont_write: has_it_been_written = self.write(doc) else: print(docing.show(doc)) quit() def register(self, event, do_write=True): self.surks = Surks() self.surks.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.surks.register_add(doc, event) if doc is None: return None if event['act'] == 'del': doc = self.surks.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 surk""" 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 def bounced(self, emad): from kapro import Kapro kapro = Kapro() doc = self.load(emad) profile_ele = doc.getroot() profile_ele.attrib['bounced'] = datetime.today().strftime('%Y-%m-%d') for repcode in self.repcodes(doc): kapro.delete(repcode, emad) self.write(doc, emad=emad)