mirror of
https://github.com/moshix/mvs.git
synced 2026-01-16 08:14:22 +00:00
386 lines
18 KiB
Python
386 lines
18 KiB
Python
#!/usr/bin/python3
|
|
|
|
"""
|
|
Split MVS 3.8j TK4- Spool files
|
|
|
|
DESCRIPTION:
|
|
1) Takes print outputs generated by MVS 3.8j TK4-.
|
|
a) either from an already ASCII file
|
|
b) or by listening directly to a sockdev
|
|
2) Splits it into one file per job.
|
|
3) Converts each job print out to PDF.
|
|
|
|
DEPENDENCIES:
|
|
- MVS 3.8j TK4-
|
|
- Phyton 3
|
|
- enscript and ps2pdf
|
|
need to installed and in the path of your computer running this.
|
|
|
|
BUGS:
|
|
* Some error when processing CLASS Z printouts
|
|
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xd7 in position 0: invalid continuation byte
|
|
|
|
CHANGELOG:
|
|
v0.4.2: Beta release - Solved bug because of typo on the months' list
|
|
v0.4.1: Beta release - Solved bug on conversion to 24h format.
|
|
v0.4.0: Beta release - Separated Programmer's Name from Job Name.
|
|
- Reverted v0.3.0, as it doesn't solve the issue.
|
|
v0.3.0: Beta release - Solved bug that created a blank page at the beginning.
|
|
v0.2.0: Beta release - If jobDateYear is the same as current date, add century to the filename (e.g. 20 => 2020).
|
|
v0.1.1: Beta release - Solved bugs with PDF filename: jobType missing, no leading zeros when hour < 10, job name truncated.
|
|
v0.1.0: Beta release - First release.
|
|
|
|
---------------------------LICENSE NOTICE--------------------------------
|
|
MIT License
|
|
|
|
Copyright (c) 2020 David Asta
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
in the Software without restriction, including without limitation the rights
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
copies of the Software, and to permit persons to whom the Software is
|
|
furnished to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in all
|
|
copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
SOFTWARE.
|
|
"""
|
|
|
|
import sys
|
|
import argparse
|
|
import subprocess
|
|
import os
|
|
import socket
|
|
import datetime
|
|
|
|
__pgmname__ = "mvssplitspl"
|
|
__version__ = "v0.4.2 Beta"
|
|
|
|
################################################################
|
|
class SplitSpool:
|
|
############################################################
|
|
# class init
|
|
def __init__(self, outdir, seproom, sepmsgclass, debug):
|
|
self.outputdir = outdir
|
|
# Job information
|
|
self.jobType = ''
|
|
self.jobNumber = ''
|
|
self.jobName = ''
|
|
self.jobProgrammerName = ''
|
|
self.jobRoom = ''
|
|
self.jobTimeHour = ''
|
|
self.jobTimeMinutes = ''
|
|
self.jobTimeSeconds = ''
|
|
self.jobTimeAMPM = ''
|
|
self.jobDateDay = ''
|
|
self.jobDateMonth = ''
|
|
self.jobDateYear = ''
|
|
self.jobPrinter = ''
|
|
self.jobMSGCLASS = ''
|
|
|
|
self.sockdev = ''
|
|
self.jobLines = list()
|
|
self.countEndJobLine = 0
|
|
self.endJob = False
|
|
self.months = ["", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"]
|
|
|
|
self.seproom = seproom
|
|
self.sepmsgclass = sepmsgclass
|
|
self.debug = debug
|
|
self.outputfilename = ''
|
|
|
|
############################################################
|
|
# Create a socket and connect to specified Address and Port
|
|
def connectToSocket(self, hostaddr, hostport):
|
|
# Create socket
|
|
try:
|
|
self.sockdev = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
except socket.error as err:
|
|
print('Error creating socket %s', err)
|
|
sys.exit(1)
|
|
|
|
# Connect to socket
|
|
try:
|
|
self.sockdev.connect((hostaddr, int(hostport)))
|
|
print('Connected to: ' + hostaddr + ":" + hostport)
|
|
except socket.error as err:
|
|
print('Error connecting to ' + hostaddr + ":" + hostport)
|
|
print(err)
|
|
sys.exit(1)
|
|
|
|
############################################################
|
|
# Listen to whatever is coming out of the MVS sockdev
|
|
# and for each line call processLine
|
|
# Lines are detected by Line Feed (\n)
|
|
def listenToSocket(self):
|
|
data = ''
|
|
buffer = ''
|
|
#isFirstChar = True
|
|
|
|
while True:
|
|
data = self.sockdev.recv(1).decode('utf-8')
|
|
|
|
# v0.3.0 - When a 0x0C character is found
|
|
# as the first character, it makes
|
|
# a Form Feed (FF), which creates
|
|
# a blank page at the beginning.
|
|
#if isFirstChar:
|
|
# isFirstChar = False
|
|
# if hex(ord(data[0:1])) == '0xc':
|
|
# data = ' '
|
|
|
|
buffer = buffer + data
|
|
if data == '\n': # Line Feed
|
|
self.processLine(buffer)
|
|
buffer = ''
|
|
|
|
############################################################
|
|
# Read all lines of the input file,
|
|
# and for each line call processLine
|
|
def readFile(self, inputfile):
|
|
isFirstChar = True
|
|
self.prtfile = open(inputfile, "r")
|
|
|
|
while True:
|
|
line = self.prtfile.readline()
|
|
|
|
if not line:
|
|
break
|
|
|
|
# v0.3.0 - When a 0x0C character is found
|
|
# as the first character, it makes
|
|
# a Form Feed (FF), which creates
|
|
# a blank page at the beginning.
|
|
#if isFirstChar:
|
|
# isFirstChar = False
|
|
# if hex(ord(line[0:1])) == '0xc':
|
|
# print("Initial FF removed")
|
|
# print(hex(ord(line[0:1])))
|
|
# line = ' ' + line[1:]
|
|
|
|
self.processLine(line)
|
|
|
|
self.prtfile.close()
|
|
|
|
############################################################
|
|
# For a line,
|
|
# extract job's information if is START/CONT
|
|
# call createJobFile if is END
|
|
def processLine(self, line):
|
|
# Line with START, identifies first page of the printout
|
|
if (line.find("START", 7, 12) > 0) \
|
|
or (line.find("CONT", 7, 12) > 0):
|
|
self.extractJobInfo(line)
|
|
# Line with END, identifies last page of the printout
|
|
elif line.find("END", 7, 12) > 0:
|
|
self.countEndJobLine = self.countEndJobLine + 1
|
|
if self.countEndJobLine == 4:
|
|
self.endJob = True
|
|
# Normal line
|
|
self.jobLines.append(line)
|
|
|
|
if self.endJob:
|
|
# All lines of all pages of this job were read
|
|
self.createJobFile(self.jobLines)
|
|
self.jobLines.clear()
|
|
self.endJob = False
|
|
self.countEndJobLine = 0
|
|
|
|
############################################################
|
|
# Get Job's information details,
|
|
# used to compose the final filename of the PDF
|
|
def extractJobInfo(self, line):
|
|
self.jobMSGCLASS = line[4:5]
|
|
self.jobNumber = line[18:22].strip()
|
|
self.jobName = line[24:32].strip()
|
|
self.jobProgrammerName = line[34:55].strip()
|
|
self.jobRoom = line[61:65].strip()
|
|
self.jobTimeHour = (str(line[67:69]).strip()).zfill(2)
|
|
self.jobTimeMinutes = line[70:72]
|
|
self.jobTimeSeconds = line[73:75]
|
|
self.jobTimeAMPM = line[76:78]
|
|
self.jobDateDay = line[79:81]
|
|
self.jobDateMonth = str(self.months.index(line[82:85])).zfill(2)
|
|
self.jobDateYear = line[86:88]
|
|
self.jobPrinter = line[90:98]
|
|
|
|
# If necessary, convert hours to 24h format
|
|
self.convertTo24h(int(self.jobTimeHour))
|
|
|
|
# Detect type of job
|
|
if line.find("JOB", 14,17) > 0:
|
|
self.jobType = 'J'
|
|
elif line.find("STC", 14,17) > 0:
|
|
self.jobType = 'S'
|
|
elif line.find("TSU", 14,17) > 0:
|
|
self.jobType = 'T'
|
|
|
|
############################################################
|
|
# Create file with Job's print out in TXT format
|
|
# Call enscript to convert from TXT to PS
|
|
# Call ps2pdf to convert from PS to PDF
|
|
# The PDF is created at the outdir specified in the
|
|
# parameters when this script was called
|
|
# Remove TXT and PS files after the conversion to PDF is done
|
|
def createJobFile(self, jobLines):
|
|
filename = self.composeOutputFilename()
|
|
dirname = self.composeOutputDirectory()
|
|
filename_prt = dirname + filename + ".prt"
|
|
filename_ps = dirname + filename + ".ps"
|
|
filename_pdf = dirname + "/" + filename + ".pdf"
|
|
|
|
print(filename_pdf)
|
|
|
|
outputfile = open(filename_prt, "w")
|
|
|
|
for line in jobLines:
|
|
outputfile.write(line)
|
|
|
|
outputfile.close()
|
|
|
|
# Call external programs enscript and ps2pdf to get the final PDF file
|
|
#subprocess.run(["enscript", "--quiet", "--font=Courier-Bold@8", "-l", "-H1", "-r", \
|
|
subprocess.run(["enscript", "--quiet", "--font=1403VintageMonoLimited-Regular@8.5", "-l", "-L 60","-H3", "-r", \
|
|
"--margins=25:25:40:40", "-p", "- ", filename_prt, "-o", filename_ps])
|
|
if not self.debug:
|
|
os.remove(filename_prt)
|
|
subprocess.run(["ps2pdf", filename_ps, filename_pdf])
|
|
os.remove(filename_ps)
|
|
|
|
############################################################
|
|
# Compose the final PDF filename,
|
|
# based on the job information
|
|
def composeOutputFilename(self):
|
|
# example: 200609_113744_A_PRINTER1-ROOM_J942_PRINTSR
|
|
|
|
# v0.2.0 - If jobDateYear is the same as current date,
|
|
# add century to the filename (e.g. 20 => 2020)
|
|
yearnow = str(datetime.datetime.now().year)[2:4]
|
|
centurynow = str(datetime.datetime.now().year)[0:2]
|
|
if(int(yearnow) == int(self.jobDateYear)):
|
|
self.jobDateYear = str(datetime.datetime.now().year)
|
|
|
|
filename = \
|
|
self.jobDateYear + self.jobDateMonth + self.jobDateDay \
|
|
+ "_" + self.jobTimeHour + self.jobTimeMinutes + self.jobTimeSeconds \
|
|
+ "_" + self.jobMSGCLASS \
|
|
+ "_" + self.jobPrinter \
|
|
|
|
# If there is a ROOM name (/*JOBPARM ROOM=xxxx), added it.
|
|
if self.jobRoom:
|
|
filename = filename \
|
|
+ '-' + self.jobRoom
|
|
|
|
filename = filename \
|
|
+ "_" + self.jobType + self.jobNumber \
|
|
+ "_" + self.jobName
|
|
|
|
if self.jobProgrammerName:
|
|
filename = filename \
|
|
+ "_" + self.jobProgrammerName
|
|
|
|
return filename
|
|
|
|
############################################################
|
|
# Compose the output dir,
|
|
# based in parameters outdir, seproom and sepmsgclass
|
|
# If not existing, create directory
|
|
def composeOutputDirectory(self):
|
|
dirname = ''
|
|
|
|
if self.seproom or self.sepmsgclass:
|
|
if self.seproom and self.sepmsgclass:
|
|
# Separate both by ROOM and MSGCLASS
|
|
if self.jobRoom: # Is there actually a ROOM specified in the printout?
|
|
self.createDir(self.outputdir + "/ROOM-" + self.jobRoom)
|
|
self.createDir(self.outputdir + "/ROOM-" + self.jobRoom + "/CLASS" + self.jobMSGCLASS)
|
|
dirname = self.outputdir + "/ROOM-" + self.jobRoom + "/CLASS" + self.jobMSGCLASS
|
|
else:
|
|
self.createDir(self.outputdir + "/CLASS" + self.jobMSGCLASS)
|
|
dirname = self.outputdir + "/CLASS" + self.jobMSGCLASS
|
|
elif self.seproom:
|
|
# Separate by ROOM only
|
|
if self.jobRoom: # Is there actually a ROOM specified in the printout?
|
|
self.createDir(self.outputdir + "/ROOM-" + self.jobRoom)
|
|
dirname = self.outputdir + "/ROOM-" + self.jobRoom
|
|
else:
|
|
self.createDir(self.outputdir)
|
|
dirname = self.outputdir
|
|
elif self.sepmsgclass:
|
|
# Separate by MSGCLASS only
|
|
self.createDir(self.outputdir + "/CLASS" + self.jobMSGCLASS)
|
|
dirname = self.outputdir + "/CLASS" + self.jobMSGCLASS
|
|
else:
|
|
self.createDir(self.outputdir)
|
|
|
|
return dirname
|
|
|
|
############################################################
|
|
# Try to create directory
|
|
def createDir(self, dirname):
|
|
if not os.path.exists(dirname):
|
|
try:
|
|
os.mkdir(dirname)
|
|
print("Directory ", dirname, " created.")
|
|
except OSError as err:
|
|
print("Directory ", dirname, " CANNOT be created.")
|
|
print(err)
|
|
sys.exit(1)
|
|
|
|
############################################################
|
|
# Convert hour to 24h format
|
|
# convert 12AM to 00
|
|
# convert from 1PM to 11Pm, to 13 to 23
|
|
def convertTo24h(self, hour):
|
|
hour24 = hour
|
|
|
|
if self.jobTimeAMPM == "PM":
|
|
if hour < 12: # convert from 1PM to 11PM, to 13 to 23
|
|
hour24 = hour + 12
|
|
elif hour == 12: # convert 12AM to 00
|
|
hour24 = 0
|
|
|
|
self.jobTimeHour = str(hour24).zfill(2)
|
|
|
|
################################################################
|
|
if __name__ == "__main__":
|
|
# Check that parameter have been received
|
|
parser = argparse.ArgumentParser( prog=__pgmname__, \
|
|
description="Split MVS 3.8j TK4- Spool files", \
|
|
epilog=__pgmname__ + " " + __version__)
|
|
parser.add_argument("-v", "--version", action="version", version="%(prog)s {version}".format(version=__version__))
|
|
parser.add_argument("-in", "--infile", help="input file (file generated by MVS)")
|
|
parser.add_argument("-a", "--address", help="IP address of host's sockdev")
|
|
parser.add_argument("-p", "--port", help="Port of host's sockdev")
|
|
parser.add_argument("outdir", help="destination directory for PDFs")
|
|
parser.add_argument("-r", "--seproom", action="store_true", default=False, help="Separate outputs per ROOM. Directory created if needed.")
|
|
parser.add_argument("-c", "--sepmsgclass", action="store_true", default=False, help="Separate outputs per MSGCLASS. Directory created if needed.")
|
|
parser.add_argument("-d", "--debug", action="store_true", default=False, help="Do not delete .prt files")
|
|
|
|
args = parser.parse_args()
|
|
|
|
# remove trailing / from outdir, if exists
|
|
if args.outdir.endswith('/'):
|
|
args.outdir = args.outdir[0:len(args.outdir) - 1]
|
|
|
|
splitter = SplitSpool(args.outdir, args.seproom, args.sepmsgclass, args.debug)
|
|
|
|
if args.infile:
|
|
# Process an already existing file
|
|
splitter.readFile(args.infile)
|
|
elif args.address and args.port:
|
|
# Connect and process from a scokdev
|
|
splitter.connectToSocket(args.address, args.port)
|
|
splitter.listenToSocket()
|
|
|
|
sys.exit(0)
|
|
|