Skip to content

Instantly share code, notes, and snippets.

@zelon88
Last active January 21, 2020 21:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save zelon88/ecbcbcd036ce870dcbd697dbb1e48e16 to your computer and use it in GitHub Desktop.
Save zelon88/ecbcbcd036ce870dcbd697dbb1e48e16 to your computer and use it in GitHub Desktop.
Display_Board_Updater_Client.py
# --------------------------------------------------
# Display_Board_Updater_Client.py
# v1.6 - 1/21/2020
# Justin Grimes (@zelon88)
# https://www.HonestRepair.net
# Made on Windows 7 with Python 2.7
# This program is for preparing & displaying PDF reports in fullscreen on unattended workstations.
# For example, you can use this program to create an up-to-date dashboard that rotates slides automatically.
# --------------------------------------------------
# --------------------------------------------------
# VALID ARGUMENTS / PARAMETERS / SWITCHES
# If you combine multiple verbosity or log levels the last specified will be used.
# h - Display help text. 1st argument.
# help - Display help text. 1st argument.
# <path to document folder> - 1st argument.
# <time in seconds to display each document> - 2nd argument.
# <time in seconds before old reports are deleted> - 3rd argument.
# s - Split multi-page PDFs into separate files & display each file separately. 4th-7th argument.
# split - Split multi-page PDFs into separate files & display each file separately. 4th-7th argument.
# v0 - Verbosity 0. Optional. Disable output. 4th-7th argument.
# v1 - Verbosity 1. Optional. Only errors are output. 4th-7th argument.
# v2 - Verbosity 2. Optional. Everything is output. 4th-7th argument.
# l0 - Log level 0. Optional. Disable logging. 4th-7th argument.
# l1 - Log level 1. Optional. Only errors logged. 4th-7th argument.
# l2 - Log level 2. Optional. Everything is logged. 4th-7th argument.
# --------------------------------------------------
# --------------------------------------------------
# EXAMPLE COMMANDS
# Display help text.
# Display_Board_Updater_Client.py h
# Prepare the documents in C:\Path-To-Files and display each one for 12s. Delete old reports after 1 hour.
# Full logging & console output.
# Display_Board_Updater_Client.py "C:\Path-To-Files" 12 3600 12 v3 l3
# Prepare the documents in C:\Path-To-Files and display each one for 30s. Delete old reports after 2 hours.
# Split multiple pages into files. No logging or console output.
# Display_Board_Updater_Client.py "C:\Path-To-Files" 30 7200 s v0 l0
# --------------------------------------------------
# --------------------------------------------------
# Load required modules and set global variables.
import sys, getopt, time, datetime, os, shutil
# -----DEFAULT CONFIGURATION SETTINGS----- #
# The filename of this application.
progFileName = "Display_Board_Updater_Client.py"
# The full name of this application.
progName = "Display_Board_Updater_Client"
# The current version of this application.
progVers = "v1.6"
# A concise description of this application.
progDesc = 'This program prepares the data created by Display_Board_Updater_Server and displays it in full screen for shop floor dashboards.'
# Prefix standard log entries with the following string.
logPrefix = 'OP-Act: '
# In the absence of the "s" argument, the following default value will be used to enable/disable splitting pages into files.
autoSplit = False
# In the absence of a "refresh time" argument, the following default value will be used for display duration.
refreshTime = 60
# In the absence of a "logging" argument, the following default value defines the log level that will be used.
logging = 1
# In the absence of a "verbosity" argument, the following default value devines the verbosity level that will be used.
verbosity = 2
# In the absence of an "expiration time", the following default value defines the number of seconds to store reports before deleting them.
expirationDuration = float(time.time()) - (60 * 60) # 60sec x 60min = 1hour
# The following integer determines an upper boundary for the main loop of this program.
# If defined, the main loop of this program will execute this many times before stopping.
# Set to "0" to enable non-stop execution.
executionLimit = 0
# The installation directory where this application is installed.
currentPath = os.path.dirname(__file__)
# If you need to specify this configuration entry with hard-coded paths, uncomment, de-space, and populate the line below with the path.
#currentPath = "C:\Display_Board_Updater_Client"
# The following string must be a valid absolute path to a place where this application can create & append to a log file.
logFile = os.path.join(currentPath, 'Display_Board_Updater_Client.log')
# -----END DEFAULT CONFIGURATION SETTINGS----- #
now = datetime.datetime.now()
realtime = now.strftime("%B %d, %Y, %H:%M")
error = inputFile = inputPath = preparedDir = pageDir = ''
errorCounter = loopCounter = 0
# --------------------------------------------------
# --------------------------------------------------
# A function to print output to the console in a consistent manner.
# Always returns 1.
def printGracefully(logPrefix, message):
print (logPrefix+message+'.')
return 1
# --------------------------------------------------
# --------------------------------------------------
# A function to kill the program gracefully during unrecoverable error.
# The errorMessage will be displayed to the user, unless the s switch is set.
# Note this uses sys.exit(), which not only kills this script but the entire interpreter.
# Should never return anything because the interpreter should be dead before the return line.
def dieGracefully(errorMessage, errorNumber, errorCounter):
print ('ERROR-'+str(errorCounter)+'!!! '+str(progName)+str(errorNumber)+': '+str(errorMessage)+' on '+str(realtime)+'!')
sys.exit()
return 1
# --------------------------------------------------
# --------------------------------------------------
# A function to write an entry to the logFile.
# Do not punctuate your log entries with punctuation, or it will look strange.
# Set the errorNumber to 0 for regular prefix (default is "OP-Act").
# If the entry is an error message, set errorNumber to an int greater than 0.
# Always returns 1.
def writeLog(logFile, logEntry, realtime, errorNumber, errorCounter):
if os.path.isfile(logFile): append = "ab"
else: append = "wb+"
if errorNumber > 0: entryPrefix = 'ERROR-'+str(errorCounter)+'!!! '+str(progName)+str(errorNumber)+': '
else: entryPrefix = logPrefix
entrySufix = ' on '+str(realtime)+'.'
with open(logFile, append) as logData:
logData.write(entryPrefix+logEntry+entrySufix+"\n")
logData.close
return 1
# --------------------------------------------------
# --------------------------------------------------
# A function to process user supplied arguments/parameters/switches.
# Returns populated variables: autoSplit, logging, verbosity, inputFile, inputPath, refreshTime, preparedDir, pageDir
def parseArgs(logging, verbosity, argv, errorCounter):
autoSplit = False
# Check if any arguments were passed.
try: opts, args = getopt.getopt(argv,"h")
except getopt.GetoptError:
print (str(progFileName)+' <path to folder> <time in seconds to display each report> <time in seconds before old reports are deleted> <options>')
if len(sys.argv) <= 1:
print ('\nType "'+str(progFileName)+' help" to display '+str(progName)+' usage & version information.\n')
sys.exit(2)
if sys.argv[1] == 'h' or sys.argv[1] == 'help':
# Print the help text if the "h" argument is passed
print ('\n'+str(progName)+' '+str(progVers)+', by Justin Grimes (@zelon88) - https://www.HonestRepair.net\n')
print ('\n'+str(progDesc)+'\n\n'+str(progFileName)+' <path to folder> <time in seconds to display each report> <time in seconds before old reports are deleted> <options>\n')
print (' Options: ')
print ('----------')
print (' h = Display Help & Version Information')
print (' help = Display Help & Version Information')
print ('----------')
print (' s = Split pages into separate files')
print (' split = Split pages into separate files')
print (' ---------')
print (' v0 = Display No Messages')
print (' v1 = Display Warning Messages Only')
print (' v2 = Display All Messages')
print (' ---------')
print (' l0 = Record No Logs')
print (' l1 = Record Warning Logs Only')
print (' l2 = Record All Logs')
sys.exit(2)
if len(sys.argv) > 4:
# Set the logging level from argument 4.
if sys.argv[4] == 'l0': logging = 0
if sys.argv[4] == 'l1': logging = 1
if sys.argv[4] == 'l2': logging = 2
# Set the verbosity level from argument 4.
if sys.argv[4] == 'v0': verbosity = 0
if sys.argv[4] == 'v1': verbosity = 1
if sys.argv[4] == 'v2': verbosity = 2
# Set autoSplit overwrite from argument 4.
if sys.argv[4] == 's' or 'split' in sys.argv[4]: autoSplit = True
if len(sys.argv) > 5:
# Set the logging level from argument 5.
if sys.argv[5] == 'l0': logging = 0
if sys.argv[5] == 'l1': logging = 1
if sys.argv[5] == 'l2': logging = 2
# Set the verbosity level from argument 5.
if sys.argv[5] == 'v0': verbosity = 0
if sys.argv[5] == 'v1': verbosity = 1
if sys.argv[5] == 'v2': verbosity = 2
# Set autoSplit overwrite from argument 5.
if sys.argv[5] == 's' or 'split' in sys.argv[5]: autoSplit = True
if len(sys.argv) > 6:
# Set the logging level from argument 6.
if sys.argv[6] == 'l0': logging = 0
if sys.argv[6] == 'l1': logging = 1
if sys.argv[6] == 'l2': logging = 2
# Set the verbosity level from argument 6.
if sys.argv[6] == 'v0': verbosity = 0
if sys.argv[6] == 'v1': verbosity = 1
if sys.argv[6] == 'v2': verbosity = 2
# Set autoSplit overwrite from argument 6.
if sys.argv[6] == 's' or 'split' in sys.argv[6]: autoSplit = True
# Check to see if a folder path was supplied.
try: sys.argv[1]
except IndexError:
errorCounter += 1
message = 'No folder path was specified'
if logging > 0: writeLog(logFile, message, realtime, 89, errorCounter)
if verbosity > 0: dieGracefully(message, 89, errorCounter)
else: sys.exit()
# Check to see if an output delimeter was supplied.
try: sys.argv[2]
except IndexError:
errorCounter += 1
message = 'No refresh time was specified'
if logging > 0: writeLog(logFile, message, realtime, 89, errorCounter)
if verbosity > 0: dieGracefully(message, 89, errorCounter)
else: sys.exit()
# Check to see if an output delimeter was supplied.
try: sys.argv[3]
except IndexError:
errorCounter += 1
message = 'No expiration time was specified'
if logging > 0: writeLog(logFile, message, realtime, 89, errorCounter)
if verbosity > 0: dieGracefully(message, 89, errorCounter)
else: sys.exit()
else:
inputFile = sys.argv[1]
inputPath = inputFile
preparedDir = os.path.join(inputPath, "Prepared")
pageDir = os.path.join(preparedDir, "Pages")
refreshTime = int(sys.argv[2])
expirationDuration = float(time.time()) - float(sys.argv[3])
# Check to see that required directories exist & create them where required.
if not os.path.exists(inputPath):
# "ERROR-<#>!!! Display_Board_Updater_Client109, The output file specified relies on an invalid directory on <time>."
errorCounter += 1
message = 'The folder path specified relies on an invalid directory'
if logging > 0: writeLog(logFile, message, realtime, 126, errorCounter)
if verbosity > 0: dieGracefully(message, 126, errorCounter)
else: sys.exit()
return autoSplit, logging, verbosity, inputFile, inputPath, refreshTime, preparedDir, pageDir, expirationDuration
# --------------------------------------------------
# --------------------------------------------------
# A function to verify that required directories exist and create them when needed.
# Returns 0 if all files exist. Returns 1 on error.
def verifyDirs(inputPath, preparedDir, pageDir):
if not os.path.exists(inputPath):
os.mkdir(inputPath)
if not os.path.exists(preparedDir):
os.mkdir(preparedDir)
if not os.path.exists(pageDir):
os.mkdir(pageDir)
if not os.path.exists(inputPath) or not os.path.exists(preparedDir) or not os.path.exists(pageDir):
return 0
else:
return 1
# --------------------------------------------------
# --------------------------------------------------
# A function to scan the specified input folder.
# Returns a list of files contained in the specified folder.
def scanFolder(scanPath):
return os.listdir(scanPath)
# --------------------------------------------------
# --------------------------------------------------
# A function to prepare the raw reports found in the inputPath.
# This function will execute pdfsplit on the inputPath and store individual pages in the pageDir.
# Pages are then copied to the preparedDir when ready and deleted from PageDir.
# Always returns 1.
def prepareReports(currentPath, inputPath, preparedDir, pageDir, expirationDuration):
os.chdir(inputPath)
pdfSplitPath = os.path.join(currentPath, 'pdfsplit.py')
inputFiles = scanFolder(inputPath)
for iFile in inputFiles:
if ".pdf" in iFile:
pageNumber = commandResult = 0
while commandResult == 0:
pageNumber += 1
iFilePath = os.path.join(inputPath, iFile)
oFilePath = os.path.join(pageDir, iFile)+'page-'+str(pageNumber)
commandResult = os.system(pdfSplitPath+' '+iFilePath+' '+oFilePath+' '+str(pageNumber))
pageFiles = scanFolder(pageDir)
for pFile in pageFiles:
pFilePath = os.path.join(pageDir, pFile)
prepFilePath = os.path.join(preparedDir, pFile)
shutil.copyfile(pFilePath, prepFilePath)
if os.path.getmtime(os.path.join(pageDir, pFile)) < expirationDuration:
os.remove(os.path.join(pageDir, pFile))
message = 'Deleting '+str(os.path.join(pageDir, pFile))+' on '+str(realtime)
if logging > 1: writeLog(logFile, message, realtime, 0, 0)
if verbosity > 1: printGracefully(logPrefix, message)
for iFile2 in inputFiles:
if ".pdf" in iFile2:
inputFilePath = os.path.join(inputPath, iFile2)
if os.path.getmtime(inputFilePath) < expirationDuration:
os.remove(inputFilePath)
message = 'Deleting '+str(inputFilePath)+' on '+str(realtime)
if logging > 1: writeLog(logFile, message, realtime, 0, 0)
if verbosity > 1: printGracefully(logPrefix, message)
inputFiles = pageFiles = iFilePath = oFilePath = iFile = pFilePath = pFile = prepFilePath = iFile2 = ""
return 1
# --------------------------------------------------
# --------------------------------------------------
# Display a prepared report for the desired duration.
# Uses Adobe Acrobad Reader DC to display PDF's in fullscreen mode.
# Kill Adobe Acrobat once the report has been displayed for refreshTime number of seconds.
# Once the report is done being displayed it is deleted so it cannot be displayed again.
# Always returns 1.
def displayReport(currentPath, displayDir):
displayFiles = scanFolder(displayDir)
for displayFile in displayFiles:
displayFilePath = os.path.join(displayDir, displayFile)
if ".pdf" in displayFilePath:
displayCommandResult = os.system('start "" /max "C:\\Program Files (x86)\\Adobe\\Acrobat Reader DC\\Reader\\AcroRd32.exe" /A "pagemode=FullScreen" "'+displayFilePath+'"')
time.sleep(float(refreshTime))
os.system("taskkill /im AcroRd32.exe")
time.sleep(.8)
if os.path.getmtime(displayFilePath) < expirationDuration:
os.remove(displayFilePath)
message = 'Deleting '+str(displayFilePath)+' on '+str(realtime)
if logging > 1: writeLog(logFile, message, realtime, 0, 0)
if verbosity > 1: printGracefully(logPrefix, message)
return 1
# --------------------------------------------------
# --------------------------------------------------
# A function to display some text to kick things off.
# Known as the welcome message.
# Always returns 1.
def printWelcome(logging, verbosity):
print ('\n')
message = 'Starting '+str(progName)+' on '+str(realtime)
if logging > 1: writeLog(logFile, message, realtime, 0, 0)
if verbosity > 1: printGracefully(logPrefix, message)
return 1
# --------------------------------------------------
# --------------------------------------------------
# A function to display some text to round things out.
# Known as the goodbye message.
# Always returns 1.
def printGoodbye(logging, verbosity):
message = 'Operation complete'
if logging > 1: writeLog(logFile, message, realtime, 0, 0)
if verbosity > 1:
printGracefully('', message)
print("\n")
return 1
# --------------------------------------------------
# --------------------------------------------------
# The main portion of the program which makes use of the functions above.
autoSplit, logging, verbosity, inputFile, inputPath, refreshTime, preparedDir, pageDir, expirationDuration = parseArgs(logging, verbosity, sys.argv[1:], errorCounter)
printWelcome(logging, verbosity)
while loopCounter <= executionLimit or executionLimit == 0:
loopCounter += 1
if verifyDirs(inputPath, preparedDir, pageDir) > 0:
if autoSplit == True:
prepareReports(currentPath, inputPath, preparedDir, pageDir, expirationDuration)
displayReport(currentPath, preparedDir)
else:
displayReport(currentPath, inputPath)
printGoodbye(logging, verbosity)
# --------------------------------------------------
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment