/*---------------------------------------------------------- Lists information about waiting mail in the "forward" directory. 2018-02-06 SHL Ensure .fwd files closed 2018-02-06 SHL Show bad version string in quotes 2018-02-06 SHL Allow forward directory to be specified on command line 2018-02-06 SHL Report number of files listed 2019-01-23 SHL Sync with Peter's 23 January 2019 revision 2019-04-29 SHL Support wildcard filespec as argument 2019-05-13 SHL Show Date: header too - it can be useful 2019-05-16 PJM Merged the changes by PJM and SHL 2019-05-21 SHL Look for INIFile in same directory as ListFwd.cmd if not in current directory 2019-05-22 PJM Changed the 'parse parameter' code 2019-06-17 PJM Check for file deleted Author: Peter Moylan (peter@pmoylan.org) Revisions: Steven Levine Last revised: 16 November 2020 Usage: listfwd list the details of all *.FWD files in the forward directory listfwd INIname to specify an INI or TNI file name, possibly including a path listfwd fwddir to specify forward directory on the command line listfwd wildcard to specify forward file wildcard on the command line If no argument is given (the default case), or if no path is specified in the wildcard case, then the current working directory must contain WEASEL.INI or WEASEL.TNI, because we need to look up that file to work out where the "forward" directory is. Remark: if you choose to leave this script in the TOOLS directory, then of course you would invoke it with tools\listfwd ------------------------------------------------------------*/ /****************************************************************/ /* MAIN PROGRAM */ /****************************************************************/ CALL RxFuncAdd SysLoadFuncs, rexxutil, sysloadfuncs CALL SysLoadFuncs CALL CheckPrerequisites SelectTNI INI_get NUMERIC DIGITS 12 DaysInMonth.1 = 31 DaysInMonth.2 = 28 DaysInMonth.3 = 31 DaysInMonth.4 = 30 DaysInMonth.5 = 31 DaysInMonth.6 = 30 DaysInMonth.7 = 31 DaysInMonth.8 = 31 DaysInMonth.9 = 30 DaysInMonth.10 = 31 DaysInMonth.11 = 30 DaysInMonth.12 = 31 InBuffer = "" Nul = '00'X INIfile = '' fwdDir = '' mask = '' PARSE UPPER ARG parameter parameter = TRANSLATE(parameter, '\', '/') /* Classify the different possibilities for the parameter. */ IF parameter \= '' THEN DO /* 2019-05-22 PJM New logic to classify argument. */ slash = LASTPOS('\', parameter) wildpos = VERIFY(parameter, '*?', 'MATCH', 1) IF wildpos > 0 THEN DO IF wildpos < slash THEN DO SAY "Sorry, directory specification may not contain wildcard characters" EXIT 1 END ELSE DO /* Normal wildcard specification. */ IF slash = 0 THEN DO mask = parameter END ELSE IF slash = LENGTH(parameter) THEN DO fwdDir = LEFT(parameter, slash - 1) END ELSE DO fwdDir = LEFT(parameter, slash - 1) mask = RIGHT(parameter, LENGTH(parameter) - slash) END END END ELSE DO /* No wildcard characters in parameter. */ ext = TRANSLATE(RIGHT(parameter, 4)) IF (ext = '.INI') | (ext = '.TNI') THEN INIfile = parameter ELSE fwdDir = parameter END END /* The parameter might or might not have specified a value for */ /* the forward directory. If not, get the information from */ /* the INI or TNI file. */ IF fwdDir = '' THEN DO CALL CheckPrerequisites SelectTNI INI_get IF INIfile = '' THEN DO IF SelectTNI("Weasel") > 0 THEN INIfile = "Weasel.TNI" ELSE INIfile = "Weasel.INI" END SAY "Using "INIfile IF STREAM(INIfile, 'C', 'QUERY EXISTS') = '' THEN DO SAY INIFile" not found, trying script directory" /* 2019-05-21 SHL Try script directory */ PARSE SOURCE . . cmd i = LASTPOS('\', cmd) s = LEFT(cmd, i) || INIFile IF STREAM(s, 'C', 'QUERY EXISTS') \== '' THEN INIFile = s ELSE DO SAY "ERROR: File "INIfile" not found" EXIT END END /* Find the 'forward' directory. */ MailRoot = INI_get(INIfile, '$SYS', 'MailRoot') j = POS(Nul,MailRoot) IF j > 0 THEN MailRoot = LEFT(MailRoot, j-1) MailRoot = STRIP(MailRoot) MailRoot = TRANSLATE(MailRoot, '\', '/') fwdDir = MailRoot||'forward' END SAY 'fwdDir='fwdDir /* Do the listing. */ IF mask = '' THEN mask = '*.fwd' IF RIGHT(fwdDir, 1) \== '\' THEN fwdDir = fwdDir || '\' wildcard = fwdDir || mask CALL SysFileTree wildcard, 'filelist', 'FO' IF filelist.0 = 0 THEN SAY "No files waiting in forward directory" ELSE DO SAY SAY "Files in the forward directory" SAY DO i = 1 to filelist.0 CALL SayFileInfo filelist.i CALL STREAM filelist.i, 'C', 'CLOSE' /* 2018-02-06 SHL */ END SAY 'Listed' filelist.0 'files' END RETURN /****************************************************************/ /* WRITE FILE INFORMATION TO STANDARD OUTPUT */ /****************************************************************/ SayFileInfo: PROCEDURE EXPOSE InFile InBuffer DaysInMonth. InFile = ARG(1) InBuffer = "" /********************************************************************/ /* While relay mail is waiting to be sent it is stored as a file */ /* which starts with the following details. */ /* 4 bytes format version, value 'V000' */ /* 4 bytes send time */ /* 1 byte retry number */ /* 1 byte Boolean flags */ /* variable sender (character string) */ /* variable recipient list, bounded by () and comma-separated */ /* */ /* The message content starts immediately after this. */ /********************************************************************/ SAY InFile CALL STREAM InFile, 'C', 'OPEN READ' /* 2018-02-06 SHL */ version = CHARIN(InFile,1,4) IF version = "" THEN DO SAY " File unavailable, possibly deleted by now" RETURN END IF version <> "V000" THEN DO SAY " Invalid format version "version", possibly not a mail file" RETURN END sendtime = C2D(REVERSE(CHARIN(InFile,,4))) retrynum = C2D(CHARIN(InFile,,1)) SAY " send time = "DateTime(sendtime)" retry number = "retrynum flags = C2D(CHARIN(InFile,,1)) IF BITAND(flags, 1) \= 0 THEN CALL CHAROUT ," Notify-on-failure" flags = flags%2 IF BITAND(flags, 1) \= 0 THEN SAY " Recirculate" ELSE SAY CALL GetMoreInput stage = 0 DO WHILE stage < 1 k = POS('(', InBuffer) IF k = 0 THEN CALL GetMoreInput ELSE DO PARSE VAR InBuffer from '(' InBuffer stage = 1 END END SAY " From: "from SAY " To: "GetBracketed() /* 2019-05-13 SHL Show Date: header */ CALL GetMoreInput /* Ensure buffer contains at least one header */ DO FOREVER PARSE VAR InBuffer 'Date: ' date '0d'x InBuffer IF date \== '' THEN LEAVE c = length(InBuffer) CALL GetMoreInput IF c == LENGTH(InBuffer) THEN LEAVE /* EOF */ END /* Should always have Date: header, but just in case */ IF date \== '' THEN SAY " Date: " || date SAY RETURN /****************************************************************/ /* */ /* GETTING A PARENTHESISED STRING */ /* */ /* Returns the leading string of the file input up to, but */ /* not including, a terminating ')'. The terminator is */ /* consumed from the input buffer. The reason why this */ /* is a separate procedure is that we have to allow for */ /* nested parentheses. */ /* */ /****************************************************************/ GetBracketed: PROCEDURE EXPOSE InFile InBuffer done = 0 result = "" DO WHILE done = 0 j = POS('(', InBuffer) k = POS(')', InBuffer) IF (j > 0) & ((k = 0) | (j < k)) THEN DO PARSE VAR InBuffer part1 '(' InBuffer result = result||part1'('||GetBracketed()||')' /* plus whatever follows */ END ELSE IF k = 0 THEN DO result = result||InBuffer InBuffer = "" dummy = GetMoreInput() END ELSE DO PARSE VAR InBuffer part1 ')' InBuffer result = result||part1 done = 1 END END RETURN result /****************************************************************/ /* GETTING MORE BUFFERED INPUT */ /****************************************************************/ GetMoreInput: PROCEDURE EXPOSE InFile InBuffer chunksize = 512 InBuffer = InBuffer||CHARIN(InFile,,chunksize) RETURN 0 /****************************************************************/ /* INTERPRETING A DATE/TIME VALUE */ /****************************************************************/ DateTime: PROCEDURE EXPOSE DaysInMonth. /************************************************************/ /* The input parameter is the time in seconds since the */ /* start of the year 1970. We return a date/time string */ /* in the form yyyy-mm-dd hh:mm:ss */ /************************************************************/ seconds = ARG(1) minutes = seconds % 60 seconds = RIGHT(seconds - 60*minutes,2,'0') hours = minutes % 60 minutes = RIGHT(minutes - 60*hours, 2, '0') days = hours % 24 hours = RIGHT(hours - 24*days, 2, '0') RETURN DayToYMD(days)' 'hours':'minutes':'seconds /****************************************************************/ DayToYMD: PROCEDURE EXPOSE DaysInMonth. /************************************************************/ /* The input parameter is the time in days since the */ /* start of the year 1970. We return a date string */ /* in the form yyyy-mm-dd */ /************************************************************/ day = ARG(1) + 1 /* to convert to 1-based counting */ year = 1970 /* Step through a year at a time. There's probably a more */ /* efficient way of doing this, but for now I can't be */ /* bothered working it out. */ DO WHILE day > 366 /* If the year we're skipping is a leap year ... */ IF (year // 4) = 0 THEN day = day - 1 year = year + 1 day = day - 365 END IF (day = 366) & ((year // 4) <> 0) THEN DO year = year + 1 day = 1 END /* We now have day and year, have to work out month. */ IF (year // 4) = 0 THEN DaysInMonth.2 = 29 month = 1 DO WHILE day > DaysInMonth.month day = day - DaysInMonth.month month = month + 1 END month = RIGHT(month, 2, '0') day = RIGHT(day, 2, '0') DaysInMonth.2 = 28 RETURN year'-'month'-'day /****************************************************************/ /* CHECKING PREREQUISITES */ /****************************************************************/ CheckPrerequisites: PROCEDURE /* The argument is a space-separated list of prerequisite */ /* functions, for example */ /* CALL CheckPrerequisites rxu SelectTNI INI_get */ /* where (at least in this version) each list item is */ /* either 'rxu' or a function from my TNItools package. */ /* If any is missing then we exit with an error message. */ PARSE UPPER ARG funclist funclist = STRIP(funclist) needrxu = 0 needtools = 0 DO WHILE funclist \= '' PARSE VAR funclist func funclist funclist = STRIP(funclist) IF func = 'RXU' THEN DO /* Initialise RXU if not already available, fail if */ /* the RxFuncAdd operation fails. We must */ /* RxFuncQuery RxuTerm because RxuTerm does not */ /* deregister RxuInit. The RxFuncDrop is needed */ /* because RxFuncAdd seems to report failure if the */ /* function is already registered. */ IF RxFuncQuery('RxuTerm') THEN DO CALL RxFuncDrop('RxuInit') CALL RxFuncAdd 'RxuInit','RXU','RxuInit' IF result THEN DO SAY 'Cannot load RXU' needrxu = 1 END ELSE CALL RxuInit END END ELSE DO func = func||'.CMD' IF SysSearchPath('PATH', func) = '' THEN DO SAY 'ERROR: 'func' must be in your PATH' needtools = 1 END END END IF needrxu THEN SAY 'You can find RXU1a.zip at Hobbes' IF needtools THEN SAY 'Please install the GenINI package' IF needrxu | needtools THEN EXIT 1 RETURN /****************************************************************/ /* TEST OF DateTime PROCEDURE */ /****************************************************************/ TimeTest: PROCEDURE EXPOSE DaysInMonth. secsperday = 60*60*24 secs = 1359423629 + 5*secsperday result = DateTime(secs) SAY secs" seconds => "result RETURN /****************************************************************/