1
0
mirror of https://github.com/moshix/mvs.git synced 2026-01-16 08:14:22 +00:00
moshix.mvs/mvssplitter.py
2021-09-10 11:59:32 -05:00

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)