#!/bin/sh # ############################################################################# # # author: Thomas Kaiser # license: GPL - http://www.gnu.org/copyleft/gpl.html # url: http://users.phg-online.de/tk/MOSXS/postscriptfile.gz # date: 20 Mar 2003 (v.0.3.0) # ############################################################################# # # Description: # # For a detailled description please visit # http://users.phg-online.de/tk/MOSXS/postscriptfile.txt # # Some misc stuff related to that can be found at # http://users.phg-online.de/tk/MOSXS/postscriptfile-stuff/ # # Some examples how to use the 'folder actionscripts' can be found at # http://users.phg-online.de/tk/MOSXS/postscriptfile-stuff/actionscripts/ # ############################################################################# # # Non-Warranty: # This script comes with absolutely no warranty. # # As there is only very limited error handling in the code this script will # probably open several security holes. # Use in a production environment isn't recommended # # Use at your own risk! # ############################################################################# # # Customizable settings: TARGET_BASEDIR=/Users/tk/Documents/PS-Files # standard folder for ps files MAX_FILENAME_LENGTH=31 # Longer filenames will be truncated to this value DEBUG=FALSE # set to TRUE to get some debug output in $DEBUG_LOG DEBUG_LOG=/Users/tk/debug.txt # where to put debug stuff when $DEBUG=TRUE? # administrative settings GROUP=staff # which group should own the PS/PDF files? FILE_PERMS=664 # which permissions for written files? PROTOCOL=`basename ${0}` # name of the protocol, we're capable. Do not # change unless you exactly know what you do! ############################################################################# # # Some functions CheckOperationMode () { # Our behaviour depends on how we're called. So you can create symlinks # named like 'distiller' or 'pstill' and this script will recognize the # specific hotfolder configurations. Let's have a look how we are called # # Specify $AppToLaunch when you want to launch an app automatically case ${PROTOCOL} in distiller) HOTFOLDER=TRUE processor_name="Distiller" search_for_name="In" options_name="folder.joboptions" in_path="In" out_path="Out" ;; pstill) HOTFOLDER=TRUE processor_name="PStill" search_for_name="ProcessedPS" options_name="*.pPref" in_path="." out_path="PDF" AppToLaunch="/Applications/PStill.app" ;; ghostscript) HOTFOLDER=TRUE processor_name="GhostScript" search_for_name="Finished" options_name="gs-options.txt" in_path="." out_path="Finished" # AppToLaunch=/usr/local/bin/gs-8 ;; pdfenhancer) HOTFOLDER=TRUE processor_name="PDF Enhancer" search_for_name="Pending" options_name="*.spe" in_path="." out_path="Pending" AppToLaunch="/Applications/PDFEnhancer.app" ;; postscriptfile) HOTFOLDER=FALSE ;; *) exit 0 ;; esac } LogMessage () { # if $DEBUG is TRUE then write something into $DEBUG_LOG if [ ${DEBUG} = "TRUE" ]; then if [ "X${AlreadyLogged}" = "X" ]; then AlreadyLogged=TRUE echo -e "------\nStart processing new CUPS request at `date`" \ >>${DEBUG_LOG} fi echo -e "$*" >>${DEBUG_LOG} fi } NotifyCUPS () { # sending messages on stderr to keep CUPS informed echo -e "$*" >&2 LogMessage " NotifyCUPS: sending \"$*\"" } CheckType () { # check whether the file is of type PDF or PS FNAME=`basename "${TMP_FILE}"` FILE_TYPE=`file "${TMP_FILE}" | sed "s:^${TMP_FILE}\:\ ::;" | cut -d" " -f1` LogMessage "We're called as \"${PROTOCOL}\"\nProcessing ${FNAME} (${CONTENT_TYPE} --> ${FILE_TYPE})" case ${FILE_TYPE} in PDF*) EXT="pdf" ;; PostScript*) EXT="ps" ;; *) EXT="unknown" ;; esac } LaunchPostProcessor () { # The following sequence tries to start an application (e.g. PStill # or PDF Enhancer), that you can define with $AppToLaunch. # If you don't want to let that happen, do not set the $AppToLaunch # variable in the "CheckOperationMode" function above # (thanks to Mark Moorcroft for this suggestion and to Leonard # Rosenthol for details about PDF Enhancer) if [ -x "${AppToLaunch}" ]; then AppName=`basename "${AppToLaunch}" .app` AppPID=`ps acx | grep ${AppName} | awk '{print $1}' | head -n1` if ! [ ${AppPID} ]; then NotifyCUPS "INFO: Launching ${AppName}" LogMessage "[${AppName} not running. Starting ${AppToLaunch} ...\c" su ${OWNER} -c "open \"${AppToLaunch}\" 2>&1 >/dev/null &" status=$? if [ ${status} -eq 0 ]; then LogMessage " done.]" else LogMessage " failed.]" fi else LogMessage "[${AppName} already running (PID ${AppPID})]" fi fi } CheckForGhostScript () { # Let's have a look whether we're called as "ghostscript". # In this case, we will read in the gs-options.txt and # invoke $AppToLaunch with the appropriate settings if [ ${PROTOCOL} = "ghostscript" ]; then if [ "${AppToLaunch}" = "None" ]; then LogMessage '$AppToLaunch set to "None". Stopped Postprocessing' return 0 # If $AppToLaunch is set to "None" then # stop further processing with GS fi if [ ! -x ${AppToLaunch:=/usr/local/bin/gs} ]; then NotifyCUPS "ERROR: Unable to execute ${AppToLaunch}" LogMessage "Cannot execute ${AppToLaunch} -- check configuration" exit 1 fi LOG="${TARGET_FILE}.log" NotifyCUPS "INFO: GhostScript processing \"`basename "${TARGET_PDF}"`\"" LogMessage "Trying to run ${AppToLaunch} `cat "${TARGET_DIR}/${options_name}" | tr "\n" " " | tr "\r" " "`\nUsing \"${TARGET_PDF}\" as OutputFile" ${AppToLaunch} `cat "${TARGET_DIR}/${options_name}" | tr "\n" " " | tr "\r" " "` -sOutputFile="${TARGET_PDF}" "${TARGET_FILE}" 2>&1 >"${LOG}" status=$? if [ ${status} -eq 0 ]; then rm "${LOG}" mv "${TARGET_FILE}" `dirname "${TARGET_DIR}"`/${PDF_FOLDER} chmod ${FILE_PERMS} "${TARGET_PDF}" /usr/sbin/chown ${OWNER}:${GROUP} "${TARGET_PDF}" NotifyCUPS "INFO: PDF creation with GhostScript suceeded" LogMessage "Successfully created ${TARGET_PDF}" else chmod ${FILE_PERMS} "${LOG}" /usr/sbin/chown ${OWNER}:${GROUP} "${LOG}" NotifyCUPS "WARNING: PDF creation with GhostScript failed" LogMessage "Failed to convert to PDF. See ${LOG} for details" fi else mv "${TMP_FILE}" "${TARGET_FILE}" chmod ${FILE_PERMS} "${TARGET_FILE}" /usr/sbin/chown ${OWNER}:${GROUP} "${TARGET_FILE}" fi } WalkThroughHotfolders () { # Let's walk through $TARGET_BASEDIR subfolders and publish # watched folders (that contain an $joboption file and a # special folder called $search_for_name) echo "file ${PROTOCOL} \"Unknown\" \"${processor_name} hotfolder\"" cd "${TARGET_BASEDIR}" find . -type f -name "${options_name}" | while read joboption; do BASEFOLDER=`dirname "${joboption}"` FOLDERNAME=`basename "${BASEFOLDER}"` if [ -d "${BASEFOLDER}/${search_for_name}" ]; then ipath=`echo "${BASEFOLDER}/${in_path}" | perl -ple 's|([^\w=\-])|sprintf( "%%%02x", ord( $1))|ge'` opath=`echo "${out_path}" | perl -ple 's|([^\w=\-])|sprintf( "%%%02x", ord( $1))|ge'` echo "file ${PROTOCOL}://${ipath}?out=${opath} \"Unknown\" \"${processor_name} hotfolder (${FOLDERNAME})\"" fi done } SearchTargetFolder () { # convert the device uri into a relative path to store the # output file. In case, the directory does not exist, the file # will be stored in $TARGET_BASEDIR instead PARSED_URI=`echo ${DEVICE_URI} | perl -ple 's/%([0-9A-Fa-f]{2})/chr(hex($1))/eg'` TARGET_PATH=`echo ${PARSED_URI} | awk -F "?" '{print $1}' | sed "s?${PROTOCOL}\:\/\/??g" | CleanupPaths` PDF_FOLDER=`echo ${PARSED_URI} | awk -F "?" '{print $2}' | sed 's?^out=??g' | CleanupPaths` LogMessage "Device-URI: ${DEVICE_URI}\nParsed-URI: ${PARSED_URI}\nTarget-Path: ${TARGET_PATH}\nPDF-Folder: ${PDF_FOLDER}" if [ -d "${TARGET_BASEDIR}/${TARGET_PATH}" ]; then TARGET_DIR="${TARGET_BASEDIR}/${TARGET_PATH}" else TARGET_DIR="${TARGET_BASEDIR}" fi NotifyCUPS "INFO: Using ${TARGET_DIR}/ for output file" } CleanupPaths () { # Remove "/./" and especially "/../" constructions from the # path. # If someone knows how to do better, please let me know :-) cat | sed 's?^\.\.\/??;s?^\.\/??;' \ | sed 's?\/\.\/?\/?g;s?\/\.$??g' \ | sed 's?\/\.\.\/?\/?g;s?\/\.\.$??g' } SaveOutputFile () { # save the file, change permissions and ownerships and check # whether GhostScript should be called # # We'll look for executables called actionscript-pre or # actionscript-post, too. If they exist at $TARGET_DIR, # we will execute them (pre before and post after saving # the file at the final location) # This might be useful to modify the files before saving them # in hotfolders (see example in the docs) or cleanup the target # directory before or to postprocess the files individually # (eg. sending them per email or storing them on a remote server # via curl or whatever you like... # we export some useful variables, so the actionscripts have # more opportunities export TMP_FILE TARGET_FILE TARGET_PDF TARGET_BASEDIR TARGET_DIR export MAX_FILENAME_LENGTH DEBUG DEBUG_LOG FILE_TYPE PDF_FOLDER export FILE_PERMS GROUP if [ -x "${TARGET_DIR}/actionscript-pre" ]; then # we found a folder actionscript to be executed before proceeding. # we'll call it with the title of the printjob, the owner of the # job and the complete CUPS options. Whether we continue or not # depends on the exit status of the script. If it's not 0 we will # won't proceed any further and leave with an exit code 1 NotifyCUPS "INFO: Calling actionscript-pre for \"`basename "${TARGET_FILE}"`\"" LogMessage "Executing actionscript-pre" "${TARGET_DIR}/actionscript-pre" "${TITLE}" "${OWNER}" if [ $? -ne 0 ]; then NotifyCUPS "WARNING: Execution of actionscript-pre failed. Exiting" exit 1 fi fi NotifyCUPS "INFO: Saving as \"`basename "${TARGET_FILE}"`\"" mv "${TMP_FILE}" "${TARGET_FILE}" chmod ${FILE_PERMS} "${TARGET_FILE}" /usr/sbin/chown ${OWNER}:${GROUP} "${TARGET_FILE}" LogMessage "(chown ${OWNER}:${GROUP} and chmod ${FILE_PERMS} suceeded, saved\c " case ${FILE_TYPE} in PDF*) LogMessage " as PDF)" ;; PostScript*) LogMessage " as PostScript file)" CheckForGhostScript ;; esac NotifyCUPS "PAGE: 1 1" if [ -x "${TARGET_DIR}/actionscript-post" ]; then NotifyCUPS "INFO: Calling actionscript-post for \"`basename "${TARGET_FILE}"`\"" LogMessage "Executing actionscript-post" "${TARGET_DIR}/actionscript-post" "${TITLE}" "${OWNER}" if [ $? -ne 0 ]; then NotifyCUPS "WARNING: Execution of actionscript-post failed. Exiting" exit 1 fi fi } ProcessOutputFile () { # we'll try to find an unique filename, also searching inside # $out_path for PDFs with that name SUFFIX="" COUNTER=0 while true; do # we loop until we find a filename that doesn't already exist SUFFIX_LENGTH=`echo $SUFFIX | wc -c` MAX_LENGTH=`expr ${MAX_FILENAME_LENGTH} - 3 - ${SUFFIX_LENGTH}` TARGET_FILE="${TARGET_DIR}"/`echo ${TITLE} | tr "[/][:]" "_" | cut -b-${MAX_LENGTH}`"${SUFFIX}".${EXT} TARGET_PDF="${TARGET_DIR}/${PDF_FOLDER}"/`echo ${TITLE} | tr "[/][:]" "_" | cut -b-${MAX_LENGTH}`"${SUFFIX}".pdf LogMessage "filename \"$TARGET_FILE\"\c" if [ -f "${TARGET_FILE}" -o -f "${TARGET_PDF}" ] ; then # check whether target file exists COUNTER=`expr $COUNTER + 1` SUFFIX=".$COUNTER" LogMessage " already exists. Skipping" else LogMessage " used." SaveOutputFile # save the file, do accounting and eventually # call postprocessing mechanisms LogMessage "CUPS request finished at `date`" break fi done } CollectAccountingData () { TITLE="${3}" OWNER="${2}" eval ParseCUPSOptions $5 LogMessage "Title: ${TITLE}, Owner: ${OWNER}" } ParseCUPSOptions() { # We parse the CUPS options, delete occurences of "com.apple.print" in # their names, preprend the variable names with "CUPS_", define # them as variables and export them # If you want to use these variables in an folderaction script then # use set to have a look what vars are exported and what values they # have LogMessage "Parsing CUPS options and exporting them (\c" i="com\.apple\.print\." while [ $# -ge 1 ]; do VarName=`echo $1 | cut -d"=" -f1 | sed "s|^$i||g;s|\.[nb]\.$||g;s|\.||g"` VarValue=`echo $1 | cut -d"=" -f2 | sed "s|\ |\\\\\ |g"` eval CUPS_${VarName}=${VarValue} eval export CUPS_${VarName} LogMessage "CUPS_${VarName}, \c" shift done LogMessage "[that's all]) done" } GetUniqueTempFile () { # try to get a unique name for our temporary file TMP_FILE=${TMPDIR:=/tmp}/${PROTOCOL}_temp_$$ while [ -e "${TMP_FILE}" ]; do TMP_FILE="${TMP_FILE}x" done } RemoveTempStuff () { test ${DEL_AFTER_PRINT} = TRUE && rm "${TMP_FILE}" } ############################################################################# # # Let's start to process the CUPS requests CheckOperationMode # Let's have a look how we're called == how we should # behave if [ ${#} = 0 ]; then # CUPS asks us, which services we can provide if [ ${HOTFOLDER} = "TRUE" ]; then # Let's have a look whether we find some hotfolders that match # our search criteria. We will also remove duplicates if we # find more than one $optionsfile per hotfolder WalkThroughHotfolders | sort | uniq else echo "file ${PROTOCOL} \"Unknown\" \"Print to Disk\"" echo "file ${PROTOCOL}://.?out=. \"Unknown\" \"Print to Standardfolder\"" fi exit 0 elif [ ${#} = 5 ]; then GetUniqueTempFile # Ensure that we use a non existing temporary file # Get print file from stdin; copies have already been handled... cat >"${TMP_FILE}" # redirect stdin in $TMP_FILE # If we get data from stdin we have to delete tmpfiles ourself DEL_AFTER_PRINT=TRUE elif [ ${#} = 6 ]; then # use the last argument as $TMP_FILE TMP_FILE="$6" DEL_AFTER_PRINT=FALSE else exit 1 fi # process the printing request -- check whether we can open $TMP_FILE to # continue if [ ! -r "${TMP_FILE}" ] ; then NotifyCUPS "ERROR: Unable to read ${TMP_FILE}. Cannot continue" exit 1 else CollectAccountingData "$@" CheckType # Let's have a look if it's PS or PDF LaunchPostProcessor # Launch PStill or PDFEnhancer if not # already running SearchTargetFolder # Look where to store the printfile ProcessOutputFile # Try to find a unique output file name # and save the file RemoveTempStuff # Cleanup exit 0 fi