Skip to content

Instantly share code, notes, and snippets.

@ftoledo
Created August 2, 2024 20:43
Show Gist options
  • Select an option

  • Save ftoledo/aa62ef4965c11023feb9cddcc2907cbf to your computer and use it in GitHub Desktop.

Select an option

Save ftoledo/aa62ef4965c11023feb9cddcc2907cbf to your computer and use it in GitHub Desktop.

Revisions

  1. ftoledo created this gist Aug 2, 2024.
    313 changes: 313 additions & 0 deletions bsostatus.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,313 @@
    #!/usr/bin/env python2

    import glob
    import os.path
    import stat
    import sys
    from datetime import datetime
    #--------------------------------------------------------------------------
    def IsoTime(t):
    return datetime.fromtimestamp(int(t)).strftime('%Y-%m-%d %H:%M:%S')
    #--------------------------------------------------------------------------
    class OBFile:
    flavours = { 'f':"Normal", 'i':"Immediate", 'c':"Continuous", 'd':"Direct", 'h':"Hold", 'o':"Normal" }

    def __init__(self, path, zone, net=-1, node=-1):
    self.fullpath = os.path.abspath(path)
    self.zone = zone
    self.net = net
    self.node = node
    self.point = 0
    self.txt = ""
    self.type = "unk"
    self.refs = []
    try:
    st = os.stat(path)
    self.mtime = st.st_mtime
    self.size = st.st_size
    self.isDir = stat.S_ISDIR(st.st_mode)
    self.fname = os.path.basename(path)
    self.root, self.ext = os.path.splitext(self.fname)
    self.ext = self.ext[1:].lower()
    if len(self.root) == 8:
    if self.net < 0:
    self.net = int(self.root[:4], 16)
    if self.node < 0:
    self.node = int(self.root[4:], 16)
    else:
    self.point = int(self.root[4:], 16)
    self.nn = str(self.zone) + ':' + str(self.net) + '/' + str(self.node)
    if self.point > 0:
    self.nn += '.' + str(self.point)
    if self.ext == "try":
    tfile = open(path)
    tfile.seek(5)
    self.txt = "Try result: " + tfile.readline().strip()
    tfile.close()
    self.type = "try"
    elif self.ext == "bsy" or self.ext == "csy":
    self.txt = ""
    self.type = "bsy"
    elif self.ext == "hld":
    self.txt = "Holding until: " + IsoTime(open(path).readline())
    self.type = "hld"
    elif self.ext == "req":
    self.txt = ""
    self.type = "req"
    elif self.ext[1:] == "ut" and self.ext[0] in "oicdh":
    self.txt = "Flavour: " + self.flavours[self.ext[0]]
    self.type = "out"
    elif self.ext[1:] == "lo" and self.ext[0] in "ficdh":
    self.txt = "Flavour: " + self.flavours[self.ext[0]]
    self.type = "flo"
    elif self.ext == "pnt" and self.isDir:
    self.txt = "Point directory"
    self.type = "pnt"
    self.obdir = OutBoundDir(path, 5, self.zone, self.net, self.node)
    except ValueError:
    self.txt = ""
    self.type = "err"

    # The following directives are documented as a standard:
    #
    # "#" - Indicates that the files should be truncated to zero-
    # length after successfully sending the file to the remote
    # system. This is normally only employed when sending compressed
    # mail (archived mail) to the remote.
    #
    # "^" - delete the file after successfully sending the file to
    # the remote system.
    #
    # "~" - skip the line from treatment. It may be useful to mark
    # an already processed line.
    #
    # <none> - indicates that the file should be sent and neither be
    # truncated nor deleted after sending. Listing the file with the
    # full path circumvents problems with files that have a name
    # starting with a character that is a known directive.
    #
    #
    # Software may optionally recognise the following directives:
    #
    # "-" As an alternate for "^"
    #
    # "!" As an alternate for "~"
    #
    # "@" Send file, do not truncate or delete.
    def OrphansToRefs(self, orphans, fname, flochar):
    for i, obfile in reversed(list(enumerate(orphans))):
    if obfile.FileName() == fname:
    obfile.flochar = flochar
    self.refs.append(obfile)
    del orphans[i]
    break

    def HandleLine(self, line, orphans):
    if line[0] in "#^~-!@":
    flochar = line[0]
    fpath = line[1:].strip()
    else:
    flochar = ' '
    fpath = line.strip()
    fname = os.path.basename(fpath)
    self.OrphansToRefs(orphans, fname, flochar)

    def GetRefFiles(self, orphans):
    if self.type == "flo":
    tfile = open(self.fullpath)
    for line in tfile:
    self.HandleLine(line, orphans)
    tfile.close()

    def NodeNumber(self):
    return self.nn if hasattr(self, 'nn') else ''

    def FileName(self):
    return self.fname if hasattr(self, 'fname') else ''

    def KnownType(self):
    return hasattr(self, 'type') and self.type != "unk" and self.type != "err"

    def Size(self):
    return " <dir>" if self.isDir else "%7d" % (self.size, )

    def FileInfoStr(self):
    return "%-12s - %s - %s" % (self.fname, IsoTime(self.mtime), self.Size())

    def Print(self, indent):
    print (' ' * indent), '+', self.FileInfoStr(), '-', self.txt
    for obfile in self.refs:
    obfile.PrintRef(indent)
    if hasattr(self, 'obdir'):
    self.obdir.PrintFiles()

    def FloActionTxt(self):
    if self.flochar == '#':
    return "Truncate after send"
    elif self.flochar == '^' or self.flochar == '-':
    return "Delete after send"
    elif self.flochar == '!' or self.flochar == '~':
    return "Skip"
    else:
    return "Keep after send"

    def PrintRef(self, indent):
    print (' ' * indent), self.flochar, self.FileInfoStr(), "- Action:", self.FloActionTxt()

    def PrintOrphan(self, indent):
    print (' ' * (indent + 2)), self.FileInfoStr()

    #--------------------------------------------------------------------------
    class OutBoundDir:
    def __init__(self, oDir, indent, zone, net=-1, node=-1):
    self.oDir = oDir
    self.zone = zone
    self.net = net
    self.node = node
    self.indent = indent
    self.nfiles = {}
    self.orphans = []

    self.AddFiles(glob.glob(os.path.join(oDir, "*")))

    def AddFiles(self, files):
    if files:
    self.FilesToLists(files)
    self.orphans.sort(key=OBFile.FileName)
    self.AddOrphansToFloFiles()

    def FilesToLists(self, files):
    for oFile in files:
    obf = OBFile(oFile, self.zone, self.net, self.node)
    if obf.KnownType():
    self.AddFile(obf)
    else:
    self.orphans += obf,

    def AddFile(self, obfile):
    if obfile.nn not in self.nfiles:
    self.nfiles[obfile.nn] = []

    self.nfiles[obfile.nn] += obfile,

    def AddOrphansToFloFiles(self):
    for key, obfiles in self.nfiles.iteritems():
    for obfile in obfiles:
    obfile.GetRefFiles(self.orphans)

    def PrintNodes(self):
    for key in sorted(self.nfiles):
    print (' ' * self.indent) + key
    for obfile in self.nfiles[key]:
    obfile.Print(self.indent)

    def PrintOrphans(self):
    if self.orphans:
    print (' ' * self.indent) + "Orphaned files:"
    for orphan in self.orphans:
    orphan.PrintOrphan(self.indent)

    def PrintFiles(self):
    self.PrintNodes()
    self.PrintOrphans()

    def NodeStrs(self):
    if self.net >= 0:
    return "Node", str(self.zone) + ':' + str(self.net) + '/' + str(self.node if self.node >= 0 else 0)
    else:
    return "Zone", str(self.zone)

    def PrintHeader(self):
    print self.oDir, "- %s %s" % self.NodeStrs()
    print

    def Print(self):
    self.PrintHeader()
    self.PrintFiles()
    print

    """
    Netmail: .?ut
    Referemce: .?lo
    "#" - truncate
    "^" "-" - delete
    "~" "!" - skip the line from treatment. It may be useful to mark an already processed line.
    <none> "@" Send file, do not truncate or delete
    file request: .req
    flavours:
    i - Immediate
    c - Continuous
    d - Direct
    h - Hold
    o/f - Normal (Netmail/Reference)
    Immediate ("iut" or "ilo")
    Continuous ("cut" or "clo")
    Direct ("dut" or "dlo")
    Normal ("out" or "flo")
    Hold ("hut" or "hlo")
    bsy (busy) control file
    csy (call) control file
    For information purposes a [bc]sy file may contain one line of
    PID information (less than 70 characters).
    hld (hold) control file
    A hld file must contain a one line string with the expiration
    of the hold period expressed in UNIX-time.
    try control file
    A try file must contain one line string with a diagnostic
    message. It is for information purposes only.
    For information purposes the second line of a try file may
    contain one line of PID information. ( < 70 characters)
    """
    #--------------------------------------------------------------------------
    class OutBoundDirs:
    def __init__(self, outbound, defzone):
    self.outbound = outbound
    self.defzone = defzone

    def Handle(self):
    if not os.path.isdir(self.outbound):
    print "Error:", self.outbound, "is not a dir."
    return 1

    self.outboundDirs = [ (self.outbound, self.defzone), ]
    self.GetOutboundDirs()
    self.ProcessOutboundDirs()
    return 0

    def GetOutboundDirs(self):
    dirs = glob.glob(self.outbound + ".*")
    dirs.sort()

    self.outboundStrLen = len(self.outbound) + 1
    for dir in dirs:
    self.GetOutboundDir(dir)

    def GetOutboundDir(self, dir):
    ext = dir[self.outboundStrLen:]
    try:
    zone = int(ext, 16)
    self.outboundDirs += ((dir, zone),)
    except ValueError:
    pass

    def ProcessOutboundDirs(self):
    for od in self.outboundDirs:
    obd = OutBoundDir(od[0], 2, od[1])
    obd.Print()

    #--------------------------------------------------------------------------
    if __name__ == "__main__":
    rv = 0
    print "bsostatus 1.1.0 Copyright (C) 2022-01-17 by Wilfred van Velzen"
    print
    if len(sys.argv) < 3:
    print "usage:", sys.argv[0], "<default outbound dir> <default zone>"
    rv = 1
    else:
    rv = OutBoundDirs(sys.argv[1], int(sys.argv[2])).Handle()

    sys.exit(rv)