You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

381 lines
14 KiB
Plaintext

#!/usr/bin/env seiscomp-python
################################################################################
# Copyright (C) 2012-2013, 2020 Helmholtz-Zentrum Potsdam - Deutsches GeoForschungsZentrum GFZ
#
# tabinvmodifier -- Tool for inventory modification using nettab files.
#
# This software is free software and comes with ABSOLUTELY NO WARRANTY.
#
# Author: Marcelo Bianchi
# Email: mbianchi@gfz-potsdam.de
################################################################################
from __future__ import print_function
import os
import sys
import datetime, time
from nettab.lineType import Nw, Sa, Na, Ia
from nettab.basesc3 import sc3
import seiscomp.datamodel, seiscomp.io, seiscomp.client, seiscomp.core, seiscomp.logging
class Rules(object):
def __init__(self, relaxed = False):
self.relaxed = relaxed
self.attributes = {}
self.iattributes = []
return
@staticmethod
def _overlaps(pstart, pend, cstart, cend):
if pend:
if pend > cstart:
if not cend or pstart < cend:
return True
else:
if not cend or pstart < cend:
return True
return False
def Nw(self, nw):
key = (nw.code, nw.start, nw.end)
if key in self.attributes:
raise Exception("Nw (%s/%s-%s) is already defined." % key)
self.attributes[key] = {}
self.attributes[key]["Sa"] = []
self.attributes[key]["Na"] = []
return key
def Sa(self, key, sa):
try:
items = self.attributes[key]["Sa"]
except KeyError:
raise Exception ("Nw %s/%s-%s not found in Ruleset" % key)
items.append(sa)
def Na(self, key, na):
try:
items = self.attributes[key]["Na"]
except KeyError:
raise Exception ("Nw %s/%s-%s not found in Ruleset" % key)
items.append(na)
def Ia(self, ia):
self.iattributes.append(ia);
def findKey(self, ncode, nstart, nend):
for (code, start, end) in self.attributes:
if code == ncode and self._overlaps(start, end, nstart, nend):
return (code, start, end)
return None
def getInstrumentsAttributes(self, elementId, elementType):
att = {}
for item in self.iattributes:
if item.match(elementId, elementType):
att[item.Key] = item.Value
return att
def getNetworkAttributes(self, key):
att = {}
for item in self.attributes[key]["Na"]:
att[item.Key] = item.Value
return att
def getStationAttributes(self, key, ncode, scode, lcode, ccode, start, end):
att = {}
for item in self.attributes[key]["Sa"]:
if item.match(scode, lcode, ccode, start, end, self.relaxed):
att[item.Key] = item.Value
return att
class InventoryModifier(seiscomp.client.Application):
def __init__(self, argc, argv):
seiscomp.client.Application.__init__(self, argc, argv)
self.setMessagingUsername("iModify")
self.rules = None
self.relaxed = False
self.outputFile = None
def _digest(self, tabFilename, rules = None):
if not tabFilename or not os.path.isfile(tabFilename):
raise Exception("Supplied filename is invalid.")
if not rules:
rules = Rules(self.relaxed)
try:
fd = open(tabFilename)
for line in fd:
obj = None
line = line.strip()
if not line or line[0] == "#": continue
if str(line).find(":") == -1:
raise Exception("Invalid line format '%s'" % line)
(Type, Content) = line.split(":",1)
if Type == "Nw":
nw = Nw(Content)
key = rules.Nw(nw)
elif Type == "Sg":
raise Exception("Type not supported.")
elif Type == "Na":
na = Na(Content)
rules.Na(key, na)
elif Type == "Sa":
sa = Sa(Content)
rules.Sa(key, sa)
elif Type == "Sr":
raise Exception("Type not supported.")
elif Type == "Ia":
ia = Ia(Content)
rules.Ia(ia)
elif Type == "Se":
raise Exception("Type not supported.")
elif Type == "Dl":
raise Exception("Type not supported.")
elif Type == "Cl":
raise Exception("Type not supported.")
elif Type == "Ff":
raise Exception("Type not supported.")
elif Type == "If":
raise Exception("Type not supported.")
elif Type == "Pz":
raise Exception("Type not supported.")
except Exception as e:
raise e
finally:
if fd:
fd.close()
return rules
def validateParameters(self):
outputFile = None
rulesFile = None
if self.commandline().hasOption("rules"):
rulesFile = self.commandline().optionString("rules")
if self.commandline().hasOption("output"):
outputFile = self.commandline().optionString("output")
if self.commandline().hasOption("relaxed"):
self.relaxed = True
if self.commandline().hasOption("inventory-db") and outputFile is None:
print("Cannot send notifiers when loading inventory from file.", file=sys.stderr)
return False
if self.commandline().unrecognizedOptions():
print("Invalid options: ", end=' ', file=sys.stderr)
for i in self.commandline().unrecognizedOptions():
print(i, end=' ', file=sys.stderr)
print("", file=sys.stderr)
return False
if not rulesFile:
print("No rule file was supplied for processing", file=sys.stderr)
return False
if not os.path.isfile(rulesFile):
argv0 = os.path.basename(self.arguments()[0])
print("%s: %s: No such file or directory" % (argv0, rulesFile), file=sys.stderr)
return False
if self.commandline().hasOption("inventory-db"):
self.setDatabaseEnabled(False, False)
self.setMessagingEnabled(False)
self.rules = self._digest(rulesFile, self.rules)
self.outputFile = outputFile
return True
def createCommandLineDescription(self):
seiscomp.client.Application.createCommandLineDescription(self)
self.commandline().addGroup("Rules")
self.commandline().addStringOption("Rules", "rules,r", "Input XML filename")
self.commandline().addOption("Rules", "relaxed,e", "Relax rules for matching NSLC items")
self.commandline().addGroup("Dump")
self.commandline().addStringOption("Dump", "output,o", "Output XML filename")
def initConfiguration(self):
value = seiscomp.client.Application.initConfiguration(self)
self.setLoggingToStdErr(True)
self.setDatabaseEnabled(True, True)
self.setMessagingEnabled(True)
self.setLoadInventoryEnabled(True)
return value
def send(self, *args):
while not self.connection().send(*args):
seiscomp.logging.warning("send failed, retrying")
time.sleep(1)
def send_notifiers(self, group):
Nsize = seiscomp.datamodel.Notifier.Size()
if Nsize > 0:
seiscomp.logging.info("trying to apply %d change%s" % (Nsize,"s" if Nsize != 1 else "" ))
else:
seiscomp.logging.info("no changes to apply")
return 0
Nmsg = seiscomp.datamodel.Notifier.GetMessage(True)
it = Nmsg.iter()
msg = seiscomp.datamodel.NotifierMessage()
maxmsg = 100
sent = 0
mcount = 0
try:
try:
while it.get():
msg.attach(seiscomp.datamodel.Notifier_Cast(it.get()))
mcount += 1
if msg and mcount == maxmsg:
sent += mcount
seiscomp.logging.debug("sending message (%5.1f %%)" % (sent / float(Nsize) * 100.0))
self.send(group, msg)
msg.clear()
mcount = 0
next(it)
except:
pass
finally:
if msg.size():
seiscomp.logging.debug("sending message (%5.1f %%)" % 100.0)
self.send(group, msg)
msg.clear()
seiscomp.logging.info("done")
return mcount
@staticmethod
def _loop(obj, count):
return [ obj(i) for i in range(count) ]
@staticmethod
def _collect(obj):
code = obj.code()
start = datetime.datetime.strptime(obj.start().toString("%Y %m %d %H %M %S"), "%Y %m %d %H %M %S")
try:
end = obj.end()
end = datetime.datetime.strptime(end.toString("%Y %m %d %H %M %S"), "%Y %m %d %H %M %S")
except:
end = None
return (code, start, end)
@staticmethod
def _modifyInventory(mode, obj, att):
valid = sc3._findValidOnes(mode)
if not att:
return
# Why repeat the code in basesc3.py (sc3::_fillSc3())?
# What about if there are existing comments/pids - won't
# this code get the count wrong?? *FIXME*
commentNum = 0
for (k,p) in att.items():
try:
if k == 'Comment':
# print('DEBUG: Adding comment', p)
if p.startswith('Grant'):
# 2020: These belong in DOI metadata, not here.
continue
c = seiscomp.datamodel.Comment()
c.setText(p)
c.setId(str(commentNum))
commentNum += 1
obj.add(c)
continue
if k == 'Pid':
print('DEBUG: Adding Pid as comment', p)
c = seiscomp.datamodel.Comment()
(typ, val) = p.split(':', 1)
s = '{"type":"%s", "value":"%s"}' % (typ.upper(), val)
c.setText(s)
c.setId('FDSNXML:Identifier/' + str(commentNum))
commentNum += 1
obj.add(c)
continue
p = valid['attributes'][k]['validator'](p)
getattr(obj, 'set'+k)(p)
except KeyError:
import string
hint = ''
if k[0] in string.lowercase:
hint = " (try '%s' instead)" % ( k[0].upper() + k[1:])
print('Modifying %s: \'%s\' is not a valid key%s' % (mode, k, hint), file=sys.stderr)
obj.update()
return
def run(self):
rules = self.rules
iv = seiscomp.client.Inventory.Instance().inventory()
if not rules:
return False
if not iv:
return False
seiscomp.logging.debug("Loaded %d networks" % iv.networkCount())
if self.outputFile is None:
seiscomp.datamodel.Notifier.Enable()
self.setInterpretNotifierEnabled(True)
for net in self._loop(iv.network, iv.networkCount()):
(ncode, nstart, nend) = self._collect(net)
key = rules.findKey(ncode, nstart, nend)
if not key: continue
att = rules.getNetworkAttributes(key)
self._modifyInventory("network", net, att)
seiscomp.logging.info("%s %s" % (ncode, att))
for sta in self._loop(net.station, net.stationCount()):
(scode, sstart, send) = self._collect(sta)
att = rules.getStationAttributes(key, ncode, scode, None, None, sstart, send)
self._modifyInventory("station", sta, att)
if att: seiscomp.logging.info(" %s %s" % (scode, att))
for loc in self._loop(sta.sensorLocation, sta.sensorLocationCount()):
(lcode, lstart, lend) = self._collect(loc)
att = rules.getStationAttributes(key, ncode, scode, lcode, None, lstart, lend)
self._modifyInventory("location", loc, att)
if att: seiscomp.logging.info(" %s %s" % (lcode, att))
for cha in self._loop(loc.stream, loc.streamCount()):
(ccode, cstart, cend) = self._collect(cha)
att = rules.getStationAttributes(key, ncode, scode, lcode, ccode, cstart, cend)
self._modifyInventory("channel", cha, att)
if att: seiscomp.logging.info(" %s %s" % (ccode, att))
for sensor in self._loop(iv.sensor, iv.sensorCount()):
att = rules.getInstrumentsAttributes(sensor.name(), "Se")
self._modifyInventory("sensor", sensor, att)
for datalogger in self._loop(iv.datalogger, iv.dataloggerCount()):
att = rules.getInstrumentsAttributes(datalogger.name(), "Dl")
self._modifyInventory("datalogger", datalogger, att)
return True
def done(self):
if self.outputFile:
ar = seiscomp.io.XMLArchive()
ar.create(self.outputFile)
ar.setFormattedOutput(True)
ar.writeObject(seiscomp.client.Inventory.Instance().inventory())
ar.close()
else:
self.send_notifiers("INVENTORY")
seiscomp.client.Application.done(self)
if __name__ == "__main__":
app = InventoryModifier(len(sys.argv), sys.argv)
sys.exit(app())