925 lines
		
	
	
		
			31 KiB
		
	
	
	
		
			Plaintext
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			925 lines
		
	
	
		
			31 KiB
		
	
	
	
		
			Plaintext
		
	
	
		
			Executable File
		
	
	
	
	
#!/usr/bin/env seiscomp-python
 | 
						|
# -*- coding: utf-8 -*-
 | 
						|
############################################################################
 | 
						|
# Copyright (C) GFZ Potsdam                                                #
 | 
						|
# All rights reserved.                                                     #
 | 
						|
#                                                                          #
 | 
						|
# GNU Affero General Public License Usage                                  #
 | 
						|
# This file may be used under the terms of the GNU Affero                  #
 | 
						|
# Public License version 3.0 as published by the Free Software Foundation  #
 | 
						|
# and appearing in the file LICENSE included in the packaging of this      #
 | 
						|
# file. Please review the following information to ensure the GNU Affero   #
 | 
						|
# Public License version 3.0 requirements will be met:                     #
 | 
						|
# https://www.gnu.org/licenses/agpl-3.0.html.                              #
 | 
						|
############################################################################
 | 
						|
 | 
						|
import sys
 | 
						|
import os
 | 
						|
import traceback
 | 
						|
import re
 | 
						|
import seiscomp.core
 | 
						|
import seiscomp.client
 | 
						|
import seiscomp.datamodel
 | 
						|
import seiscomp.io
 | 
						|
import seiscomp.logging
 | 
						|
import seiscomp.system
 | 
						|
 | 
						|
 | 
						|
def time2str(time):
 | 
						|
    """
 | 
						|
    Convert a seiscomp.core.Time to a string
 | 
						|
    """
 | 
						|
    return time.toString("%Y-%m-%d %H:%M:%S.%f000000")[:23]
 | 
						|
 | 
						|
 | 
						|
def createDirectory(dir):
 | 
						|
    if os.access(dir, os.W_OK):
 | 
						|
        return True
 | 
						|
 | 
						|
    try:
 | 
						|
        os.makedirs(dir)
 | 
						|
        return True
 | 
						|
    except:
 | 
						|
        return False
 | 
						|
 | 
						|
 | 
						|
def originStatusToChar(org):
 | 
						|
    # Manual origin are always tagged as M
 | 
						|
    try:
 | 
						|
        if org.evaluationMode() == seiscomp.datamodel.MANUAL:
 | 
						|
            return "M"
 | 
						|
    except:
 | 
						|
        pass
 | 
						|
 | 
						|
    try:
 | 
						|
        if org.evaluationStatus() == seiscomp.datamodel.PRELIMINARY:
 | 
						|
            return "P"
 | 
						|
        elif (
 | 
						|
            org.evaluationStatus() == seiscomp.datamodel.CONFIRMED
 | 
						|
            or org.evaluationStatus() == seiscomp.datamodel.REVIEWED
 | 
						|
            or org.evaluationStatus() == seiscomp.datamodel.FINAL
 | 
						|
        ):
 | 
						|
            return "C"
 | 
						|
        elif org.evaluationStatus() == seiscomp.datamodel.REJECTED:
 | 
						|
            return "X"
 | 
						|
        elif org.evaluationStatus() == seiscomp.datamodel.REPORTED:
 | 
						|
            return "R"
 | 
						|
    except:
 | 
						|
        pass
 | 
						|
 | 
						|
    return "A"
 | 
						|
 | 
						|
 | 
						|
class CachePopCallback(seiscomp.datamodel.CachePopCallback):
 | 
						|
    def __init__(self, target):
 | 
						|
        seiscomp.datamodel.CachePopCallback.__init__(self)
 | 
						|
        self.target = target
 | 
						|
 | 
						|
    def handle(self, obj):
 | 
						|
        self.target.objectAboutToPop(obj)
 | 
						|
 | 
						|
 | 
						|
class EventHistory(seiscomp.client.Application):
 | 
						|
    def __init__(self, argc, argv):
 | 
						|
        seiscomp.client.Application.__init__(self, argc, argv)
 | 
						|
        seiscomp.datamodel.Notifier.SetEnabled(False)
 | 
						|
 | 
						|
        self.setMessagingEnabled(True)
 | 
						|
        self.setDatabaseEnabled(True, True)
 | 
						|
        self.setMessagingUsername("scevtlog")
 | 
						|
        self.setPrimaryMessagingGroup(seiscomp.client.Protocol.LISTENER_GROUP)
 | 
						|
        self.addMessagingSubscription("EVENT")
 | 
						|
        self.addMessagingSubscription("LOCATION")
 | 
						|
        self.addMessagingSubscription("MAGNITUDE")
 | 
						|
 | 
						|
        self.setAutoApplyNotifierEnabled(True)
 | 
						|
        self.setInterpretNotifierEnabled(True)
 | 
						|
 | 
						|
        # Create a callback object that gets called when an object
 | 
						|
        # is going to be removed from the cache
 | 
						|
        self._popCallback = CachePopCallback(self)
 | 
						|
 | 
						|
        # Create an object cache of half an hour
 | 
						|
        self._cache = seiscomp.datamodel.PublicObjectTimeSpanBuffer(
 | 
						|
            self.query(), seiscomp.core.TimeSpan(30.0 * 60.0)
 | 
						|
        )
 | 
						|
        self._cache.setPopCallback(self._popCallback)
 | 
						|
 | 
						|
        # Event progress counter
 | 
						|
        self._eventProgress = dict()
 | 
						|
 | 
						|
        # Event-Origin mapping
 | 
						|
        self._eventToOrg = dict()
 | 
						|
        self._orgToEvent = dict()
 | 
						|
 | 
						|
        # Event-Magnitude mapping
 | 
						|
        self._eventToMag = dict()
 | 
						|
        self._magToEvent = dict()
 | 
						|
 | 
						|
        self._directory = "@LOGDIR@/events"
 | 
						|
        self._format = "xml"
 | 
						|
        self._currentDirectory = ""
 | 
						|
        self._revisionFileExt = ".zip"
 | 
						|
        self._useGZIP = False
 | 
						|
 | 
						|
    def createCommandLineDescription(self):
 | 
						|
        try:
 | 
						|
            self.commandline().addGroup("Storage")
 | 
						|
            self.commandline().addStringOption(
 | 
						|
                "Storage",
 | 
						|
                "directory,o",
 | 
						|
                "Specify the storage directory. " "Default: @LOGDIR@/events.",
 | 
						|
            )
 | 
						|
            self.commandline().addStringOption(
 | 
						|
                "Storage",
 | 
						|
                "format,f",
 | 
						|
                "Specify storage format (autoloc1, autoloc3, xml [default])",
 | 
						|
            )
 | 
						|
        except:
 | 
						|
            seiscomp.logging.warning(f"caught unexpected error {sys.exc_info()}")
 | 
						|
        return True
 | 
						|
 | 
						|
    def initConfiguration(self):
 | 
						|
        if not seiscomp.client.Application.initConfiguration(self):
 | 
						|
            return False
 | 
						|
 | 
						|
        try:
 | 
						|
            self._directory = self.configGetString("directory")
 | 
						|
        except:
 | 
						|
            pass
 | 
						|
 | 
						|
        try:
 | 
						|
            self._format = self.configGetString("format")
 | 
						|
        except:
 | 
						|
            pass
 | 
						|
 | 
						|
        try:
 | 
						|
            if self.configGetBool("gzip"):
 | 
						|
                self._useGZIP = True
 | 
						|
                self._revisionFileExt = ".gz"
 | 
						|
        except:
 | 
						|
            pass
 | 
						|
 | 
						|
        return True
 | 
						|
 | 
						|
    def printUsage(self):
 | 
						|
        print(
 | 
						|
            """Usage:
 | 
						|
  scevtlog [options]
 | 
						|
 | 
						|
Save event history into files"""
 | 
						|
        )
 | 
						|
 | 
						|
        seiscomp.client.Application.printUsage(self)
 | 
						|
 | 
						|
        print(
 | 
						|
            """Examples:
 | 
						|
Execute on command line with debug output
 | 
						|
  scevtlog --debug
 | 
						|
"""
 | 
						|
        )
 | 
						|
 | 
						|
    def init(self):
 | 
						|
        if not seiscomp.client.Application.init(self):
 | 
						|
            return False
 | 
						|
 | 
						|
        try:
 | 
						|
            self._directory = self.commandline().optionString("directory")
 | 
						|
        except:
 | 
						|
            pass
 | 
						|
 | 
						|
        try:
 | 
						|
            self._format = self.commandline().optionString("format")
 | 
						|
        except:
 | 
						|
            pass
 | 
						|
 | 
						|
        if (
 | 
						|
            self._format != "autoloc1"
 | 
						|
            and self._format != "autoloc3"
 | 
						|
            and self._format != "xml"
 | 
						|
        ):
 | 
						|
            self._format = "xml"
 | 
						|
 | 
						|
        try:
 | 
						|
            if self._directory[-1] != "/":
 | 
						|
                self._directory = self._directory + "/"
 | 
						|
        except:
 | 
						|
            pass
 | 
						|
 | 
						|
        if self._directory:
 | 
						|
            self._directory = seiscomp.system.Environment.Instance().absolutePath(
 | 
						|
                self._directory
 | 
						|
            )
 | 
						|
            sys.stderr.write(f"Logging events to {self._directory}\n")
 | 
						|
 | 
						|
        self._cache.setDatabaseArchive(self.query())
 | 
						|
        return True
 | 
						|
 | 
						|
    # def run(self):
 | 
						|
    # obj = self._cache.get(seiscomp.datamodel.Magnitude, "or080221153929#16#netMag.mb")
 | 
						|
 | 
						|
    # self.updateObject(obj)
 | 
						|
    # return True
 | 
						|
 | 
						|
    def done(self):
 | 
						|
        seiscomp.client.Application.done(self)
 | 
						|
        self._cache.setDatabaseArchive(None)
 | 
						|
 | 
						|
    def printEvent(self, evt, newEvent):
 | 
						|
        if self._format != "xml":
 | 
						|
            self.printEventProcAlert(evt, newEvent)
 | 
						|
        else:
 | 
						|
            self.printEventXML(evt, newEvent)
 | 
						|
        self.advanceEventProgress(evt.publicID())
 | 
						|
 | 
						|
    def getSummary(self, time, org, mag):
 | 
						|
        strTime = time.toString("%Y-%m-%d %H:%M:%S")
 | 
						|
        summary = [strTime, "", "", "", "", "", "", "", "", ""]
 | 
						|
 | 
						|
        if org:
 | 
						|
            tim = org.time().value()
 | 
						|
            latency = time - tim
 | 
						|
 | 
						|
            summary[1] = "%5d.%02d" % (
 | 
						|
                latency.seconds() / 60,
 | 
						|
                (latency.seconds() % 60) * 100 / 60,
 | 
						|
            )
 | 
						|
 | 
						|
            lat = org.latitude().value()
 | 
						|
            lon = org.longitude().value()
 | 
						|
 | 
						|
            dep = "%7s" % "---"
 | 
						|
            try:
 | 
						|
                dep = f"{org.depth().value():7.0f}"
 | 
						|
                summary[4] = dep
 | 
						|
            except:
 | 
						|
                summary[4] = "%7s" % ""
 | 
						|
 | 
						|
            phases = "%5s" % "---"
 | 
						|
            try:
 | 
						|
                phases = "%5d" % org.quality().usedPhaseCount()
 | 
						|
                summary[5] = phases
 | 
						|
            except:
 | 
						|
                summary[5] = "%5s" % ""
 | 
						|
 | 
						|
            summary[2] = f"{lat:7.2f}"
 | 
						|
            summary[3] = f"{lon:7.2f}"
 | 
						|
 | 
						|
            try:
 | 
						|
                summary[9] = originStatusToChar(org)
 | 
						|
            except:
 | 
						|
                summary[9] = "-"
 | 
						|
 | 
						|
        if mag:
 | 
						|
            summary[6] = "%12s" % mag.type()
 | 
						|
            summary[7] = f"{mag.magnitude().value():5.2f}"
 | 
						|
            try:
 | 
						|
                summary[8] = "%5d" % mag.stationCount()
 | 
						|
            except:
 | 
						|
                summary[8] = "     "
 | 
						|
        else:
 | 
						|
            summary[6] = "%12s" % ""
 | 
						|
            summary[7] = "     "
 | 
						|
            summary[8] = "     "
 | 
						|
 | 
						|
        return summary
 | 
						|
 | 
						|
    def printEventProcAlert(self, evt, newEvent):
 | 
						|
        now = seiscomp.core.Time.GMT()
 | 
						|
 | 
						|
        org = self._cache.get(seiscomp.datamodel.Origin, evt.preferredOriginID())
 | 
						|
        prefmag = self._cache.get(
 | 
						|
            seiscomp.datamodel.Magnitude, evt.preferredMagnitudeID()
 | 
						|
        )
 | 
						|
 | 
						|
        summary = self.getSummary(now, org, prefmag)
 | 
						|
 | 
						|
        # Load arrivals
 | 
						|
        if org.arrivalCount() == 0:
 | 
						|
            self.query().loadArrivals(org)
 | 
						|
 | 
						|
        # Load station magnitudes
 | 
						|
        if org.stationMagnitudeCount() == 0:
 | 
						|
            self.query().loadStationMagnitudes(org)
 | 
						|
 | 
						|
        # Load magnitudes
 | 
						|
        if org.magnitudeCount() == 0:
 | 
						|
            self.query().loadMagnitudes(org)
 | 
						|
 | 
						|
        picks = []
 | 
						|
        amps = []
 | 
						|
 | 
						|
        if org:
 | 
						|
            narr = org.arrivalCount()
 | 
						|
            for i in range(narr):
 | 
						|
                picks.append(
 | 
						|
                    self._cache.get(seiscomp.datamodel.Pick, org.arrival(i).pickID())
 | 
						|
                )
 | 
						|
 | 
						|
            nstamags = org.stationMagnitudeCount()
 | 
						|
            for i in range(nstamags):
 | 
						|
                amps.append(
 | 
						|
                    self._cache.get(
 | 
						|
                        seiscomp.datamodel.Amplitude,
 | 
						|
                        org.stationMagnitude(i).amplitudeID(),
 | 
						|
                    )
 | 
						|
                )
 | 
						|
 | 
						|
        netmag = {}
 | 
						|
        nmag = org.magnitudeCount()
 | 
						|
 | 
						|
        bulletin = seiscomp.scbulletin.Bulletin(None, self._format)
 | 
						|
        try:
 | 
						|
            txt = bulletin.printEvent(evt)
 | 
						|
        except:
 | 
						|
            txt = ""
 | 
						|
 | 
						|
        if self._directory is None:
 | 
						|
            sys.stdout.write("%s" % ("#<\n" + txt + "#>\n"))
 | 
						|
            sys.stdout.flush()
 | 
						|
        else:
 | 
						|
            # Use created time to look up the proper directory
 | 
						|
            try:
 | 
						|
                arNow = evt.creationInfo().creationTime().get()
 | 
						|
            # Otherwise use now (in case that event.created has not been set
 | 
						|
            # which is always valid within the SC3 distribution
 | 
						|
            except:
 | 
						|
                arNow = now.get()
 | 
						|
            seiscomp.logging.error(
 | 
						|
                "directory is "
 | 
						|
                + self._directory
 | 
						|
                + "/".join(["%.2d" % i for i in arNow[1:4]])
 | 
						|
                + "/"
 | 
						|
                + evt.publicID()
 | 
						|
                + "/"
 | 
						|
            )
 | 
						|
 | 
						|
            directory = (
 | 
						|
                self._directory
 | 
						|
                + "/".join(["%.2d" % i for i in arNow[1:4]])
 | 
						|
                + "/"
 | 
						|
                + evt.publicID()
 | 
						|
                + "/"
 | 
						|
            )
 | 
						|
            if directory != self._currentDirectory:
 | 
						|
                if createDirectory(directory) == False:
 | 
						|
                    seiscomp.logging.error(f"Unable to create directory {directory}")
 | 
						|
                    return
 | 
						|
 | 
						|
            self._currentDirectory = directory
 | 
						|
            self.writeLog(
 | 
						|
                self._currentDirectory
 | 
						|
                + self.convertID(evt.publicID())
 | 
						|
                + "."
 | 
						|
                + ("%06d" % self.eventProgress(evt.publicID(), directory)),
 | 
						|
                txt,
 | 
						|
                "w",
 | 
						|
            )
 | 
						|
            self.writeLog(
 | 
						|
                self._currentDirectory + self.convertID(evt.publicID()) + ".last",
 | 
						|
                txt,
 | 
						|
                "w",
 | 
						|
            )
 | 
						|
            self.writeLog(self._directory + "last", txt, "w")
 | 
						|
            self.writeLog(
 | 
						|
                self._currentDirectory + self.convertID(evt.publicID()) + ".summary",
 | 
						|
                "|".join(summary),
 | 
						|
                "a",
 | 
						|
                "# Layout: Timestamp, +OT (minutes, decimal), Latitude, Longitude, Depth, PhaseCount, MagType, Magnitude, MagCount",
 | 
						|
            )
 | 
						|
 | 
						|
        seiscomp.logging.info("cache size = %d" % self._cache.size())
 | 
						|
 | 
						|
    def printEventXML(self, evt, newEvent):
 | 
						|
        now = seiscomp.core.Time.GMT()
 | 
						|
 | 
						|
        # Load comments
 | 
						|
        if evt.commentCount() == 0:
 | 
						|
            self.query().loadComments(evt)
 | 
						|
 | 
						|
        # Load origin references
 | 
						|
        if evt.originReferenceCount() == 0:
 | 
						|
            self.query().loadOriginReferences(evt)
 | 
						|
 | 
						|
        # Load event descriptions
 | 
						|
        if evt.eventDescriptionCount() == 0:
 | 
						|
            self.query().loadEventDescriptions(evt)
 | 
						|
 | 
						|
        org = self._cache.get(seiscomp.datamodel.Origin, evt.preferredOriginID())
 | 
						|
 | 
						|
        if evt.preferredFocalMechanismID():
 | 
						|
            fm = self._cache.get(
 | 
						|
                seiscomp.datamodel.FocalMechanism, evt.preferredFocalMechanismID()
 | 
						|
            )
 | 
						|
        else:
 | 
						|
            fm = None
 | 
						|
 | 
						|
        # Load comments
 | 
						|
        if org.commentCount() == 0:
 | 
						|
            self.query().loadComments(org)
 | 
						|
 | 
						|
        # Load arrivals
 | 
						|
        if org.arrivalCount() == 0:
 | 
						|
            self.query().loadArrivals(org)
 | 
						|
        prefmag = self._cache.get(
 | 
						|
            seiscomp.datamodel.Magnitude, evt.preferredMagnitudeID()
 | 
						|
        )
 | 
						|
 | 
						|
        wasEnabled = seiscomp.datamodel.PublicObject.IsRegistrationEnabled()
 | 
						|
        seiscomp.datamodel.PublicObject.SetRegistrationEnabled(False)
 | 
						|
 | 
						|
        ep = seiscomp.datamodel.EventParameters()
 | 
						|
        evt_cloned = seiscomp.datamodel.Event.Cast(evt.clone())
 | 
						|
        ep.add(evt_cloned)
 | 
						|
 | 
						|
        summary = self.getSummary(now, org, prefmag)
 | 
						|
 | 
						|
        if fm:
 | 
						|
            ep.add(fm)
 | 
						|
 | 
						|
            seiscomp.datamodel.PublicObject.SetRegistrationEnabled(wasEnabled)
 | 
						|
 | 
						|
            # Load focal mechainsm references
 | 
						|
            if evt.focalMechanismReferenceCount() == 0:
 | 
						|
                self.query().loadFocalMechanismReferences(evt)
 | 
						|
 | 
						|
            # Load moment tensors
 | 
						|
            if fm.momentTensorCount() == 0:
 | 
						|
                self.query().loadMomentTensors(fm)
 | 
						|
 | 
						|
            seiscomp.datamodel.PublicObject.SetRegistrationEnabled(False)
 | 
						|
 | 
						|
            # Copy focal mechanism reference
 | 
						|
            fm_ref = evt.focalMechanismReference(
 | 
						|
                seiscomp.datamodel.FocalMechanismReferenceIndex(fm.publicID())
 | 
						|
            )
 | 
						|
            if fm_ref:
 | 
						|
                fm_ref_cloned = seiscomp.datamodel.FocalMechanismReference.Cast(
 | 
						|
                    fm_ref.clone()
 | 
						|
                )
 | 
						|
                if fm_ref_cloned is None:
 | 
						|
                    fm_ref_cloned = seiscomp.datamodel.FocalMechanismReference(
 | 
						|
                        fm.publicID()
 | 
						|
                    )
 | 
						|
                evt_cloned.add(fm_ref_cloned)
 | 
						|
 | 
						|
            nmt = fm.momentTensorCount()
 | 
						|
            for i in range(nmt):
 | 
						|
                mt = fm.momentTensor(i)
 | 
						|
                if not mt.derivedOriginID():
 | 
						|
                    continue
 | 
						|
 | 
						|
                # Origin already added
 | 
						|
                if ep.findOrigin(mt.derivedOriginID()) is not None:
 | 
						|
                    continue
 | 
						|
 | 
						|
                seiscomp.datamodel.PublicObject.SetRegistrationEnabled(wasEnabled)
 | 
						|
                derivedOrigin = self._cache.get(
 | 
						|
                    seiscomp.datamodel.Origin, mt.derivedOriginID()
 | 
						|
                )
 | 
						|
                seiscomp.datamodel.PublicObject.SetRegistrationEnabled(False)
 | 
						|
 | 
						|
                if derivedOrigin is None:
 | 
						|
                    seiscomp.logging.warning(
 | 
						|
                        f"derived origin for MT {mt.derivedOriginID()} not found"
 | 
						|
                    )
 | 
						|
                    continue
 | 
						|
 | 
						|
                # Origin has been read from database -> read all childs
 | 
						|
                if not self._cache.cached():
 | 
						|
                    seiscomp.datamodel.PublicObject.SetRegistrationEnabled(wasEnabled)
 | 
						|
                    self.query().load(derivedOrigin)
 | 
						|
                    seiscomp.datamodel.PublicObject.SetRegistrationEnabled(False)
 | 
						|
 | 
						|
                # Add it to the event parameters
 | 
						|
                ep.add(derivedOrigin)
 | 
						|
 | 
						|
        if org:
 | 
						|
            seiscomp.datamodel.PublicObject.SetRegistrationEnabled(wasEnabled)
 | 
						|
 | 
						|
            # Load magnitudes
 | 
						|
            if org.magnitudeCount() == 0:
 | 
						|
                self.query().loadMagnitudes(org)
 | 
						|
 | 
						|
            if org.stationMagnitudeCount() == 0:
 | 
						|
                self.query().loadStationMagnitudes(org)
 | 
						|
 | 
						|
            seiscomp.datamodel.PublicObject.SetRegistrationEnabled(False)
 | 
						|
 | 
						|
            # Copy event comments
 | 
						|
            ncmts = evt.commentCount()
 | 
						|
            for i in range(ncmts):
 | 
						|
                cmt_cloned = seiscomp.datamodel.Comment.Cast(evt.comment(i).clone())
 | 
						|
                evt_cloned.add(cmt_cloned)
 | 
						|
 | 
						|
            # Copy origin references
 | 
						|
            org_ref = evt.originReference(
 | 
						|
                seiscomp.datamodel.OriginReferenceIndex(org.publicID())
 | 
						|
            )
 | 
						|
            if org_ref:
 | 
						|
                org_ref_cloned = seiscomp.datamodel.OriginReference.Cast(
 | 
						|
                    org_ref.clone()
 | 
						|
                )
 | 
						|
                if org_ref_cloned is None:
 | 
						|
                    org_ref_cloned = seiscomp.datamodel.OriginReference(org.publicID())
 | 
						|
                evt_cloned.add(org_ref_cloned)
 | 
						|
 | 
						|
            # Copy event descriptions
 | 
						|
            for i in range(evt.eventDescriptionCount()):
 | 
						|
                ed_cloned = seiscomp.datamodel.EventDescription.Cast(
 | 
						|
                    evt.eventDescription(i).clone()
 | 
						|
                )
 | 
						|
                evt_cloned.add(ed_cloned)
 | 
						|
 | 
						|
            org_cloned = seiscomp.datamodel.Origin.Cast(org.clone())
 | 
						|
            ep.add(org_cloned)
 | 
						|
 | 
						|
            # Copy origin comments
 | 
						|
            ncmts = org.commentCount()
 | 
						|
            for i in range(ncmts):
 | 
						|
                cmt_cloned = seiscomp.datamodel.Comment.Cast(org.comment(i).clone())
 | 
						|
                org_cloned.add(cmt_cloned)
 | 
						|
 | 
						|
            # Copy arrivals
 | 
						|
            narr = org.arrivalCount()
 | 
						|
            for i in range(narr):
 | 
						|
                arr_cloned = seiscomp.datamodel.Arrival.Cast(org.arrival(i).clone())
 | 
						|
                org_cloned.add(arr_cloned)
 | 
						|
 | 
						|
                seiscomp.datamodel.PublicObject.SetRegistrationEnabled(wasEnabled)
 | 
						|
                pick = self._cache.get(seiscomp.datamodel.Pick, arr_cloned.pickID())
 | 
						|
                seiscomp.datamodel.PublicObject.SetRegistrationEnabled(False)
 | 
						|
 | 
						|
                if pick:
 | 
						|
                    pick_cloned = seiscomp.datamodel.Pick.Cast(pick.clone())
 | 
						|
 | 
						|
                    # Load comments
 | 
						|
                    if pick.commentCount() == 0:
 | 
						|
                        self.query().loadComments(pick)
 | 
						|
 | 
						|
                    # Copy pick comments
 | 
						|
                    ncmts = pick.commentCount()
 | 
						|
                    for i in range(ncmts):
 | 
						|
                        cmt_cloned = seiscomp.datamodel.Comment.Cast(
 | 
						|
                            pick.comment(i).clone()
 | 
						|
                        )
 | 
						|
                        pick_cloned.add(cmt_cloned)
 | 
						|
                    ep.add(pick_cloned)
 | 
						|
 | 
						|
            # Copy network magnitudes
 | 
						|
            nmag = org.magnitudeCount()
 | 
						|
            for i in range(nmag):
 | 
						|
                mag = org.magnitude(i)
 | 
						|
 | 
						|
                mag_cloned = seiscomp.datamodel.Magnitude.Cast(mag.clone())
 | 
						|
 | 
						|
                seiscomp.datamodel.PublicObject.SetRegistrationEnabled(wasEnabled)
 | 
						|
                if mag.stationMagnitudeContributionCount() == 0:
 | 
						|
                    self.query().loadStationMagnitudeContributions(mag)
 | 
						|
                seiscomp.datamodel.PublicObject.SetRegistrationEnabled(False)
 | 
						|
 | 
						|
                # Copy magnitude references
 | 
						|
                nmagref = mag.stationMagnitudeContributionCount()
 | 
						|
                for j in range(nmagref):
 | 
						|
                    mag_ref_cloned = (
 | 
						|
                        seiscomp.datamodel.StationMagnitudeContribution.Cast(
 | 
						|
                            mag.stationMagnitudeContribution(j).clone()
 | 
						|
                        )
 | 
						|
                    )
 | 
						|
                    mag_cloned.add(mag_ref_cloned)
 | 
						|
 | 
						|
                org_cloned.add(mag_cloned)
 | 
						|
 | 
						|
            # Copy station magnitudes and station amplitudes
 | 
						|
            smag = org.stationMagnitudeCount()
 | 
						|
            amp_map = dict()
 | 
						|
            for i in range(smag):
 | 
						|
                mag_cloned = seiscomp.datamodel.StationMagnitude.Cast(
 | 
						|
                    org.stationMagnitude(i).clone()
 | 
						|
                )
 | 
						|
                org_cloned.add(mag_cloned)
 | 
						|
                if (mag_cloned.amplitudeID() in amp_map) == False:
 | 
						|
                    amp_map[mag_cloned.amplitudeID()] = True
 | 
						|
                    seiscomp.datamodel.PublicObject.SetRegistrationEnabled(wasEnabled)
 | 
						|
                    amp = self._cache.get(
 | 
						|
                        seiscomp.datamodel.Amplitude, mag_cloned.amplitudeID()
 | 
						|
                    )
 | 
						|
                    seiscomp.datamodel.PublicObject.SetRegistrationEnabled(False)
 | 
						|
                    if amp:
 | 
						|
                        amp_cloned = seiscomp.datamodel.Amplitude.Cast(amp.clone())
 | 
						|
                        ep.add(amp_cloned)
 | 
						|
 | 
						|
        seiscomp.datamodel.PublicObject.SetRegistrationEnabled(wasEnabled)
 | 
						|
 | 
						|
        # archive.create(event.publicID() + )
 | 
						|
        ar = seiscomp.io.XMLArchive()
 | 
						|
        ar.setFormattedOutput(True)
 | 
						|
 | 
						|
        if self._directory is None:
 | 
						|
            sys.stdout.write("#<\n")
 | 
						|
            ar.create("-")
 | 
						|
            ar.writeObject(ep)
 | 
						|
            ar.close()
 | 
						|
            sys.stdout.write("#>\n")
 | 
						|
            sys.stdout.flush()
 | 
						|
        else:
 | 
						|
            # Use created time to look up the proper directory
 | 
						|
            try:
 | 
						|
                arNow = evt.creationInfo().creationTime().get()
 | 
						|
            # Otherwise use now (in case that event.created has not been set
 | 
						|
            # which is always valid within the SC3 distribution
 | 
						|
            except:
 | 
						|
                arNow = now.get()
 | 
						|
 | 
						|
            directory = (
 | 
						|
                self._directory
 | 
						|
                + "/".join(["%.2d" % i for i in arNow[1:4]])
 | 
						|
                + "/"
 | 
						|
                + evt.publicID()
 | 
						|
                + "/"
 | 
						|
            )
 | 
						|
            if directory != self._currentDirectory:
 | 
						|
                if createDirectory(directory) == False:
 | 
						|
                    seiscomp.logging.error(f"Unable to create directory {directory}")
 | 
						|
                    return
 | 
						|
 | 
						|
            self._currentDirectory = directory
 | 
						|
            # self.writeLog(self._currentDirectory + evt.publicID(), "#<\n" + txt + "#>\n")
 | 
						|
            # self.writeLog(self._currentDirectory + evt.publicID() + ".last", txt, "w")
 | 
						|
            ar.create(
 | 
						|
                self._currentDirectory
 | 
						|
                + self.convertID(evt.publicID())
 | 
						|
                + "."
 | 
						|
                + ("%06d" % self.eventProgress(evt.publicID(), directory))
 | 
						|
                + ".xml"
 | 
						|
                + self._revisionFileExt
 | 
						|
            )
 | 
						|
            ar.setCompression(True)
 | 
						|
            if self._useGZIP:
 | 
						|
                ar.setCompressionMethod(seiscomp.io.XMLArchive.GZIP)
 | 
						|
            ar.writeObject(ep)
 | 
						|
            ar.close()
 | 
						|
            # Write last file to root
 | 
						|
            ar.create(self._directory + "last.xml" + self._revisionFileExt)
 | 
						|
            ar.setCompression(True)
 | 
						|
            if self._useGZIP:
 | 
						|
                ar.setCompressionMethod(seiscomp.io.XMLArchive.GZIP)
 | 
						|
            ar.writeObject(ep)
 | 
						|
            ar.close()
 | 
						|
            # Write last xml
 | 
						|
            ar.create(
 | 
						|
                self._currentDirectory + self.convertID(evt.publicID()) + ".last.xml"
 | 
						|
            )
 | 
						|
            ar.setCompression(False)
 | 
						|
            ar.writeObject(ep)
 | 
						|
            ar.close()
 | 
						|
            self.writeLog(
 | 
						|
                self._currentDirectory + self.convertID(evt.publicID()) + ".summary",
 | 
						|
                "|".join(summary),
 | 
						|
                "a",
 | 
						|
                "# Layout: Timestamp, +OT (minutes, decimal), Latitude, Longitude, Depth, PhaseCount, MagType, Magnitude, MagCount",
 | 
						|
            )
 | 
						|
 | 
						|
        del ep
 | 
						|
 | 
						|
    def convertID(self, id):
 | 
						|
        """Converts an ID containing slashes to one without slashes"""
 | 
						|
        p = re.compile("/")
 | 
						|
        return p.sub("_", id)
 | 
						|
 | 
						|
    def writeLog(self, file, text, mode="a", header=None):
 | 
						|
        of = open(file, mode)
 | 
						|
        if of:
 | 
						|
            if of.tell() == 0 and not header is None:
 | 
						|
                of.write(header + "\n")
 | 
						|
            of.write(text + "\n")
 | 
						|
            of.close()
 | 
						|
        else:
 | 
						|
            seiscomp.logging.error(f"Unable to write file: {file}")
 | 
						|
 | 
						|
    def objectAboutToPop(self, obj):
 | 
						|
        try:
 | 
						|
            evt = seiscomp.datamodel.Event.Cast(obj)
 | 
						|
            if evt:
 | 
						|
                try:
 | 
						|
                    self._orgToEvent.pop(evt.preferredOriginID())
 | 
						|
                    self._eventToOrg.pop(evt.publicID())
 | 
						|
 | 
						|
                    self._magToEvent.pop(evt.preferredMagnitudeID())
 | 
						|
                    self._eventToMag.pop(evt.publicID())
 | 
						|
 | 
						|
                    self._eventProgress.pop(evt.publicID())
 | 
						|
                    return
 | 
						|
                except:
 | 
						|
                    pass
 | 
						|
 | 
						|
            org = seiscomp.datamodel.Origin.Cast(obj)
 | 
						|
            if org:
 | 
						|
                try:
 | 
						|
                    self._orgToEvent.pop(org.publicID())
 | 
						|
                except:
 | 
						|
                    pass
 | 
						|
                return
 | 
						|
 | 
						|
            mag = seiscomp.datamodel.Magnitude.Cast(obj)
 | 
						|
            if mag:
 | 
						|
                try:
 | 
						|
                    self._magToEvent.pop(mag.publicID())
 | 
						|
                except:
 | 
						|
                    pass
 | 
						|
                return
 | 
						|
        except:
 | 
						|
            info = traceback.format_exception(*sys.exc_info())
 | 
						|
            for i in info:
 | 
						|
                sys.stderr.write(i)
 | 
						|
            sys.exit(-1)
 | 
						|
 | 
						|
    def eventProgress(self, evtID, directory):
 | 
						|
        # The progress is already stored
 | 
						|
        if evtID in self._eventProgress:
 | 
						|
            return self._eventProgress[evtID]
 | 
						|
 | 
						|
        # Find the maximum file counter
 | 
						|
        maxid = -1
 | 
						|
        files = os.listdir(directory)
 | 
						|
        for file in files:
 | 
						|
            if os.path.isfile(directory + file) == False:
 | 
						|
                continue
 | 
						|
            fid = file[len(evtID + ".") : len(file)]
 | 
						|
            sep = fid.find(".")
 | 
						|
            if sep == -1:
 | 
						|
                sep = len(fid)
 | 
						|
            fid = fid[0:sep]
 | 
						|
            try:
 | 
						|
                nid = int(fid)
 | 
						|
            except:
 | 
						|
                continue
 | 
						|
            if nid > maxid:
 | 
						|
                maxid = nid
 | 
						|
 | 
						|
        maxid = maxid + 1
 | 
						|
        self._eventProgress[evtID] = maxid
 | 
						|
        return maxid
 | 
						|
 | 
						|
    def advanceEventProgress(self, evtID):
 | 
						|
        try:
 | 
						|
            self._eventProgress[evtID] = self._eventProgress[evtID] + 1
 | 
						|
        except:
 | 
						|
            pass
 | 
						|
 | 
						|
    def addObject(self, parentID, object):
 | 
						|
        try:
 | 
						|
            obj = seiscomp.datamodel.Event.Cast(object)
 | 
						|
            if obj:
 | 
						|
                self._cache.feed(obj)
 | 
						|
                self._eventProgress[obj.publicID()] = 0
 | 
						|
                self.printEvent(obj, True)
 | 
						|
                self.updateCache(obj)
 | 
						|
                return
 | 
						|
 | 
						|
            # New Magnitudes or Origins are not important for
 | 
						|
            # the history update but we feed it into the cache to
 | 
						|
            # access them faster later on in case they will become
 | 
						|
            # preferred entities
 | 
						|
            obj = seiscomp.datamodel.Magnitude.Cast(object)
 | 
						|
            if obj:
 | 
						|
                self._cache.feed(obj)
 | 
						|
                return
 | 
						|
 | 
						|
            obj = seiscomp.datamodel.Origin.Cast(object)
 | 
						|
            if obj:
 | 
						|
                self._cache.feed(obj)
 | 
						|
                return
 | 
						|
 | 
						|
            obj = seiscomp.datamodel.Pick.Cast(object)
 | 
						|
            if obj:
 | 
						|
                self._cache.feed(obj)
 | 
						|
                return
 | 
						|
 | 
						|
            obj = seiscomp.datamodel.Amplitude.Cast(object)
 | 
						|
            if obj:
 | 
						|
                self._cache.feed(obj)
 | 
						|
                return
 | 
						|
 | 
						|
        except:
 | 
						|
            info = traceback.format_exception(*sys.exc_info())
 | 
						|
            for i in info:
 | 
						|
                sys.stderr.write(i)
 | 
						|
            sys.exit(-1)
 | 
						|
 | 
						|
    def updateObject(self, parentID, object):
 | 
						|
        try:
 | 
						|
            obj = seiscomp.datamodel.Event.Cast(object)
 | 
						|
            if obj:
 | 
						|
                self._cache.feed(obj)
 | 
						|
                self.printEvent(obj, False)
 | 
						|
                self.updateCache(obj)
 | 
						|
                return
 | 
						|
 | 
						|
            # Updates of a Magnitude are only imported when it is
 | 
						|
            # the preferred one.
 | 
						|
            obj = seiscomp.datamodel.Magnitude.Cast(object)
 | 
						|
            if obj:
 | 
						|
                try:
 | 
						|
                    evtID = self._magToEvent[obj.publicID()]
 | 
						|
                    if evtID:
 | 
						|
                        self._cache.feed(obj)
 | 
						|
                        evt = self._cache.get(seiscomp.datamodel.Event, evtID)
 | 
						|
                        if evt:
 | 
						|
                            self.printEvent(evt, False)
 | 
						|
                        else:
 | 
						|
                            sys.stderr.write(
 | 
						|
                                "Unable to fetch event for ID '%s' while update of magnitude '%s'\n"
 | 
						|
                                % (evtID, obj.publicID())
 | 
						|
                            )
 | 
						|
                    else:
 | 
						|
                        # Magnitude has not been associated to an event yet
 | 
						|
                        pass
 | 
						|
                except:
 | 
						|
                    # Search the corresponding event from the database
 | 
						|
                    evt = self.query().getEventByPreferredMagnitudeID(obj.publicID())
 | 
						|
                    # Associate the event (even if None) with the magnitude ID
 | 
						|
                    if evt:
 | 
						|
                        self._magToEvent[obj.publicID()] = evt.publicID()
 | 
						|
                        self._cache.feed(obj)
 | 
						|
                        self.printEvent(evt, False)
 | 
						|
                    else:
 | 
						|
                        self._magToEvent[obj.publicID()] = None
 | 
						|
                return
 | 
						|
 | 
						|
            # Usually we do not update origins. To have it complete,
 | 
						|
            # this case will be supported as well
 | 
						|
            obj = seiscomp.datamodel.Origin.Cast(object)
 | 
						|
            if obj:
 | 
						|
                try:
 | 
						|
                    evtID = self._orgToEvent[obj.publicID()]
 | 
						|
                    if evtID:
 | 
						|
                        self._cache.feed(obj)
 | 
						|
                        evt = self._cache.get(seiscomp.datamodel.Event, evtID)
 | 
						|
                        if evt:
 | 
						|
                            self.printEvent(evt, False)
 | 
						|
                        else:
 | 
						|
                            sys.stderr.write(
 | 
						|
                                "Unable to fetch event for ID '%s' while update of origin '%s'\n"
 | 
						|
                                % (evtID, obj.publicID())
 | 
						|
                            )
 | 
						|
                    else:
 | 
						|
                        # Origin has not been associated to an event yet
 | 
						|
                        pass
 | 
						|
                except:
 | 
						|
                    # Search the corresponding event from the database
 | 
						|
                    evt = self.query().getEvent(obj.publicID())
 | 
						|
                    if evt:
 | 
						|
                        if evt.preferredOriginID() != obj.publicID():
 | 
						|
                            evt = None
 | 
						|
 | 
						|
                    # Associate the event (even if None) with the origin ID
 | 
						|
                    if evt:
 | 
						|
                        self._orgToEvent[obj.publicID()] = evt.publicID()
 | 
						|
                        self._cache.feed(obj)
 | 
						|
                        self.printEvent(evt, False)
 | 
						|
                    else:
 | 
						|
                        self._orgToEvent[obj.publicID()] = None
 | 
						|
                return
 | 
						|
 | 
						|
            return
 | 
						|
 | 
						|
        except:
 | 
						|
            info = traceback.format_exception(*sys.exc_info())
 | 
						|
            for i in info:
 | 
						|
                sys.stderr.write(i)
 | 
						|
            sys.exit(-1)
 | 
						|
 | 
						|
    def updateCache(self, evt):
 | 
						|
        # Event-Origin update
 | 
						|
        try:
 | 
						|
            orgID = self._eventToOrg[evt.publicID()]
 | 
						|
            if orgID != evt.preferredOriginID():
 | 
						|
                self._orgToEvent.pop(orgID)
 | 
						|
        except:
 | 
						|
            # origin not yet registered
 | 
						|
            pass
 | 
						|
 | 
						|
        # Bind the current preferred origin ID to the event and
 | 
						|
        # vice versa
 | 
						|
        self._orgToEvent[evt.preferredOriginID()] = evt.publicID()
 | 
						|
        self._eventToOrg[evt.publicID()] = evt.preferredOriginID()
 | 
						|
 | 
						|
        # Event-Magnitude update
 | 
						|
        try:
 | 
						|
            magID = self._eventToMag[evt.publicID()]
 | 
						|
            if magID != evt.preferredMagnitudeID():
 | 
						|
                self._magToEvent.pop(magID)
 | 
						|
        except:
 | 
						|
            # not yet registered
 | 
						|
            pass
 | 
						|
 | 
						|
        # Bind the current preferred magnitude ID to the event and
 | 
						|
        # vice versa
 | 
						|
        self._magToEvent[evt.preferredMagnitudeID()] = evt.publicID()
 | 
						|
        self._eventToMag[evt.publicID()] = evt.preferredMagnitudeID()
 | 
						|
 | 
						|
 | 
						|
app = EventHistory(len(sys.argv), sys.argv)
 | 
						|
sys.exit(app())
 |