home *** CD-ROM | disk | FTP | other *** search
/ back2roots/padua / padua.7z / padua / uucp / XStat114.lha / XStat.mod < prev    next >
Encoding:
Text File  |  1993-12-25  |  63.9 KB  |  2,098 lines

  1. (***************************************************************************
  2.  :Program.    XStat
  3.  :Author.     Jⁿrgen Weinelt
  4.  :Address.    Zur Kanzel 1, D-97762 Hammelburg, Germany
  5.  :Address.    EMail: jow@rz.uni-wuerzburg.de (preferred)
  6.  :Address.    EMail: jow@hcast.franken.de
  7.  :Address.    EMail: jow@hcast.adsp.sub.org (avoid if possible)
  8.  :Version.    $VER: XStat_Source 1.14 (25-DEC-93)
  9.  :Copyright.  Freeware
  10.  :Language.   Modula-2
  11.  :Translator. M2Amiga V4.2
  12.  :History.    1.00 initial release
  13.  :History.    1.01 bug fix: used to guru when xfer = 0 bytes
  14.  :History.    1.02 added peak cps rating
  15.  :History.    1.03 added monthly statistics
  16.  :History.    1.04 switched to DosL.ReadArgs(), fixed nasty current date
  17.  :History.         bug with WB2.1 (problems with locale and "TODAY")
  18.  :History.    1.05 added per host statistics
  19.  :History.    1.06 integrated "per host" statistics into "HOSTNAME" option
  20.  :History.         (pattern matching using dos.library)
  21.  :History.    1.07 bug fix: linked list of Statistics-records was not
  22.  :History.         sorted correctly
  23.  :History.    1.08 added STDERR option; replaced all "°"'s with "av."
  24.  :History.    1.09 added "LOCAL" and "IGNORE" to XStat.data
  25.  :History.    1.10 added multiple names feature to XStat.data N-lines
  26.  :History.         fixed a small bug in the NI/NO feature
  27.  :History.    1.11 multiple XStat.data N-lines implemented
  28.  :History.         XStat.data N-line entry ".default." implemented
  29.  :History.         Xferstat "EOF" bug fixed
  30.  :History.         string length bumped to 256
  31.  :History.    1.12 added ".illegal." for missing host names in XFerStat
  32.  :History.         XStat now skips the 1st 10 chars of the 2nd XferStat line
  33.  :History.    1.13 output now shows the first and last date that was used
  34.  :History.         added "PERIOD" and "LAST" options
  35.  :History.    1.14 added XSTATFORM, XSTATVERBFORM, "flat rate" and
  36.  :History.         "negotiation time offset"; quiet turns off ⌐-notice
  37.  :Contents.   Extracts statistics from UUCiCo "Xferstat" logfile. Requires
  38.  :Contents.   one of the "Xferstat" UUCICOs (tested with the SWB UUCiCo
  39.  :Contents.   and the wUUCP UUCiCo - thanks Charly & wusel!)
  40.  **************************************************************************)
  41.  
  42.  
  43.  
  44. MODULE XStat;
  45. (*$ StackChk:=FALSE *)
  46. (*$ CaseChk:=FALSE *)
  47. (*$ Volatile:=FALSE *)
  48.  
  49.  
  50.  
  51. (*$ DEFINE BETA:=FALSE *)
  52.  
  53.  
  54.  
  55. IMPORT Arts;
  56. IMPORT ASCII;
  57. IMPORT Break;
  58. IMPORT Conversions;
  59. IMPORT DosD;
  60. IMPORT DosL;
  61. IMPORT ExecD;
  62. IMPORT ExecL;
  63. IMPORT InOut;
  64. IMPORT R;
  65. IMPORT RealConversions;
  66. IMPORT RealInOut;
  67. IMPORT String;
  68.  
  69. IMPORT SYSTEM;
  70.  
  71.  
  72.  
  73. CONST
  74.  strlen=255;        (* was 100 before 1.11 *)
  75.  
  76.  formlen=8191;      (* buffer for the output forms *)
  77.  
  78.  defverbform="&cd &8HN &FD &IC &LC &3OT &2UN &6CO &7GR &7GS &7NR &7NS &4GC &4NC&LF";
  79.  
  80.  defstatform="&LF"+
  81.              "&LF"+
  82.              "&HN: Connection statistics for &CD calls.&LF"+
  83.              "&LF"+
  84.              "Transfer statistics from &FD to &TD&LF"+
  85.              "&LF"+
  86.              "number of connects  &08AC&LF"+
  87.              "local connects      &08LC&LF"+
  88.              "ignored connects    &08IC&LF"+
  89.              "&LF"+
  90.              "total online time   &08OT     seconds&LF"+
  91.              "total phone units   &08UN     units&LF"+
  92.              "total phone cost    &12CO &CS&LF"+
  93.              "average online time &08ot     seconds/connect&LF"+
  94.              "average phone units &12un units/connect&LF"+
  95.              "average phone cost  &12co &CS/connect&LF"+
  96.              "&LF"+
  97.              "total gross read    &08GR     bytes&LF"+
  98.              "total gross send    &08GS     bytes&LF"+
  99.              "total net read      &08NR     bytes&LF"+
  100.              "total net send      &08NS     bytes&LF"+
  101.              "average gross read  &08gr     bytes/connect&LF"+
  102.              "average gross send  &08gs     bytes/connect&LF"+
  103.              "average net read    &08nr     bytes/connect&LF"+
  104.              "average net send    &08ns     bytes/connect&LF"+
  105.              "&LF"+
  106.              "average speed       &08GC     CPS (gross data transfer)&LF"+
  107.              "fastest connect     &08gp     CPS (gross data transfer)&LF"+
  108.              "average speed       &08NC     CPS (net data transfer)&LF"+
  109.              "fastest connect     &08np     CPS (net data transfer)&LF"+
  110.              "&LF"+
  111.              "average cost        &12CG &CS/MB (gross data transfer)&LF"+
  112.              "average cost        &12CN &CS/MB (net data transfer)&LF"+
  113.              "&LF";
  114.  
  115.  
  116.  
  117.  smult=1;           (* seconds per second :-) *)
  118.  mmult=60*smult;    (* seconds per minute *)
  119.  hmult=60*mmult;    (* seconds per hour *)
  120.  dmult=24*hmult;    (* seconds per day *)
  121.  
  122.  dsun=0*dmult;      (* sunday offset into week (in seconds) *)
  123.  dmon=1*dmult;      (* monday offset into week (in seconds) *)
  124.  dtue=2*dmult;      (* tuesday offset into week (in seconds) *)
  125.  dwed=3*dmult;      (* wednesday offset into week (in seconds) *)
  126.  dthu=4*dmult;      (* thursday offset into week (in seconds) *)
  127.  dfri=5*dmult;      (* friday offset into week (in seconds) *)
  128.  dsat=6*dmult;      (* saturday offset into week (in seconds) *)
  129.  
  130.  defxstatdata="UULIB:XStat.data";
  131.  defxferstat="UUSPOOL:XferStat";
  132.  
  133.  vers="XStat" (*$ IF BETA *) +"_BETA" (*$ ENDIF *) +" 1.14";
  134.  version="$VER: "+vers+" (25-DEC-93)";
  135.  argTemplate="FILE/K,DATA/K,V=VERBOSE/S,M=MONTH/K,NI=NOINCOM/S,NO=NOOUTGO/S,"+
  136.              "Q=QUIET/S,H=HOSTNAME/K,FD=FROMDATE/K,TD=TODATE/K,SE=STDERR/S,"+
  137.              "P=PERIOD/N,L=LAST/N";
  138.  
  139.  extendedhelp="\nXStat "+argTemplate+"\n"+
  140.              "\n DATA      XStat.data file\t\t  def="+defxstatdata+
  141.              "\n FILE      Xferstat file\t\t  def="+defxferstat+
  142.              "\n VERBOSE   verbose mode switch  \t  def=off"+
  143.              "\n MONTH     monthly, override fd/td\t  def=off (format MMM-YY)"+
  144.              "\n NOINCOM   don't evaluate incoming calls  def=off"+
  145.              "\n NOOUTGO   don't evaluate outgoing calls  def=off"+
  146.              "\n QUIET     suppress non-fatal errors\t  def=off"+
  147.              "\n HOSTNAME  evaluate calls to host\t  def=all"+
  148.              "\n FROMDATE  ignore calls before date\t  def=01-JAN-78"+
  149.              "\n TODATE    ignore calls after date\t  def=current date/time"+
  150.              "\n STDERR    send error messages to StdErr  def=off"+
  151.              "\n PERIOD    n days from fd, override td\t  def=0"+
  152.              "\n LAST      n days before td, override fd  def=0\n\n";
  153.  
  154.  
  155.  
  156. TYPE
  157.  StringT=ARRAY[0..strlen] OF CHAR;
  158.  StringTPtr=POINTER TO StringT;
  159.  
  160.  StrPtr=StringTPtr;                         (* just a dummy declaration     *)
  161.  
  162.  IntPtr=POINTER TO LONGINT;                 (* only needed because of the   *)
  163.                                             (* brain dead /N-handling in    *)
  164.                                             (* DosL.ReadArgs()              *)
  165.  
  166.  FormBuffer=ARRAY[0..formlen] OF CHAR;      (* buffer for the output forms  *)
  167.  
  168.  
  169.  
  170.  DosArgs=
  171.    RECORD
  172.      file: StrPtr;
  173.      data: StrPtr;
  174.      verb: LONGINT;
  175.      month: StrPtr;
  176.      noin: LONGINT;
  177.      noout: LONGINT;
  178.      quiet: LONGINT;
  179.      host: StrPtr;
  180.      fdate: StrPtr;
  181.      tdate: StrPtr;
  182.      stderr: LONGINT;
  183.      period: IntPtr;
  184.      last: IntPtr;
  185.    END;
  186.  
  187.  
  188.  
  189.  File=
  190.    RECORD
  191.      fh: DosD.FileHandlePtr;
  192.      eof: BOOLEAN;
  193.    END;
  194.  
  195.  
  196.  
  197.  CostPtr=POINTER TO Cost;
  198.  Cost=
  199.    RECORD
  200.      next: CostPtr;
  201.      start: LONGINT;        (* start time for mpu and unit                  *)
  202.      stop: LONGINT;         (* end time for mpu and unit                    *)
  203.      mpu: REAL;             (* price per unit                               *)
  204.      unit: REAL;            (* unit duration                                *)
  205.    END;
  206.  
  207.  
  208.  
  209.  XSDataPtr=POINTER TO XSData;
  210.  XSData=
  211.    RECORD
  212.      next: XSDataPtr;
  213.      host: StringT;       (* host name                                    *)
  214.      local: BOOLEAN;      (* count transmissions, no cost, no units       *)
  215.      ignore: BOOLEAN;     (* ignore this host entirely                    *)
  216.      double: BOOLEAN;     (* if true, don't free the attached costlist    *)
  217.      offset: LONGINT;     (* connection negotiation offset (in seconds)   *)
  218.      costlist: CostPtr;   (* list of Cost records                         *)
  219.    END;
  220.  
  221.  
  222.  
  223.  Connect=
  224.    RECORD
  225.      host: StringT;                 (* host name *)
  226.      callout: BOOLEAN;              (* TRUE: outgoing call *)
  227.      start: LONGINT;                (* session start time *)
  228.      stop: LONGINT;                 (* session end time *)
  229.      sdate: DosD.Date;              (* session start time in dos format *)
  230.      inbb: LONGINT;                 (* gross bytes in *)
  231.      outbb: LONGINT;                (* gross bytes out *)
  232.      inbn: LONGINT;                 (* net bytes in *)
  233.      outbn: LONGINT;                (* net bytes out *)
  234.      cost: REAL;                    (* connection phone costs *)
  235.      units: LONGINT;                (* phone units consumed *)
  236.      seconds: LONGINT;              (* online time in seconds *)
  237.      local: BOOLEAN;                (* was local -> no cost, no units *)
  238.      ignore: BOOLEAN;               (* do not count this at all *)
  239.    END;
  240.  
  241.  
  242.  
  243.   StatisticsPtr=POINTER TO Statistics;
  244.   Statistics=
  245.     RECORD
  246.       next: StatisticsPtr;
  247.       name: StringT;
  248.       in: BOOLEAN;
  249.       connects: LONGINT;
  250.       inbb: LONGINT;
  251.       outbb: LONGINT;
  252.       inbn: LONGINT;
  253.       outbn: LONGINT;
  254.       cost: REAL;
  255.       units: LONGINT;
  256.       seconds: LONGINT;
  257.       bpeak: LONGINT;
  258.       npeak: LONGINT;
  259.       localconnects: LONGINT;
  260.       ignoreconnects: LONGINT;
  261.     END;
  262.  
  263.  
  264.  
  265.   Args=
  266.     RECORD
  267.       xstatdata: StringT;           (* xstat.data name and path;          *)
  268.       xferstat: StringT;            (* xferstat name and path;            *)
  269.       verbose: BOOLEAN;             (* lotsa output;              ; off   *)
  270.       from: DosD.Date;              (* from date;                 ; zero  *)
  271.       to: DosD.Date;                (* to date;                   ; TODAY *)
  272.       host: ARRAY[0..400] OF CHAR;  (* host name pattern;         ; all   *)
  273.       in: BOOLEAN;                  (* process incoming calls;    ; on    *)
  274.       out: BOOLEAN;                 (* process outgoing calls;    ; on    *)
  275.       quiet: BOOLEAN;               (* suppress non-fatal errors; ; off   *)
  276.       monthly: BOOLEAN;             (* monthly mode;              ; off   *)
  277.       perhost: BOOLEAN;             (* perhost mode;              ; off   *)
  278.       stderr: BOOLEAN;              (* stderr mode;               ; off   *)
  279.    END;
  280.  
  281.  
  282.  
  283. VAR
  284.   inf: File;
  285.   ln1,ln2,temp: StringT;
  286.   log: Connect;
  287.   totali,totalo: Statistics;
  288.   xsroot: XSDataPtr;
  289.   args: Args;
  290.   currency: StringT;
  291.   rdargsPtr: DosD.RDArgsPtr;
  292.   statroot: StatisticsPtr;
  293.   index: INTEGER;
  294.   stdErr: DosD.FileHandlePtr;
  295.   statform,verbform: FormBuffer;
  296.  
  297.  
  298.  
  299. (*---------------------------------------------------------------------------
  300.   FreeCostChain: FreeMem() a linked list of "Cost" records
  301.   ---------------------------------------------------------------------------*)
  302. PROCEDURE FreeCostChain(d:CostPtr);
  303.   VAR
  304.     n: CostPtr;
  305.   BEGIN
  306.     REPEAT
  307.       n:=d^.next;
  308.       ExecL.FreeMem(d,SIZE(d^));
  309.       d:=n;
  310.     UNTIL n=NIL;
  311.   END FreeCostChain;
  312.  
  313.  
  314.  
  315. (*---------------------------------------------------------------------------
  316.   FreeStat: FreeMem() a linked list of "Statistics" records
  317.   ---------------------------------------------------------------------------*)
  318. PROCEDURE FreeStat(s: StatisticsPtr);
  319.   VAR
  320.     n: StatisticsPtr;
  321.   BEGIN
  322.     (* some memory may be lost if an error occurs while the XStat.data file  *)
  323.     (* is being parsed... that's because the n^.tmp field is not checked     *)
  324.     (* here. the problem is, checking n^.tmp might cause a "memory freed     *)
  325.     (* twice" guru under certain conditions. losing a few bytes seems like   *)
  326.     (* the smaller evil in this case.                                        *)
  327.     REPEAT
  328.       n:=s^.next;
  329.       ExecL.FreeMem(s,SIZE(s^));
  330.       s:=n;
  331.     UNTIL n=NIL;
  332.   END FreeStat;
  333.  
  334.  
  335.  
  336. (*---------------------------------------------------------------------------
  337.   FreeXData: FreeMem() a linked list of "XSData" records, freeing the
  338.              attached "Cost" record lists as well (if double=FALSE)
  339.   ---------------------------------------------------------------------------*)
  340. PROCEDURE FreeXData(d: XSDataPtr);
  341.   VAR
  342.     n: XSDataPtr;
  343.   BEGIN
  344.     REPEAT
  345.       n:=d^.next;
  346.       IF (d^.costlist#NIL) AND (NOT d^.double) THEN
  347.         FreeCostChain(d^.costlist);
  348.       END;
  349.       ExecL.FreeMem(d,SIZE(d^));
  350.       d:=n;
  351.     UNTIL n=NIL;
  352.   END FreeXData;
  353.  
  354.  
  355.  
  356. (*---------------------------------------------------------------------------
  357.   MyError: Display error messages, terminate program if necessary
  358.            redirect errors to STDERR if requested by new 1.08 cli switch
  359.   ---------------------------------------------------------------------------*)
  360. (*$ CopyDyn:=FALSE *)
  361. PROCEDURE MyError(x1,x2,x3: ARRAY OF CHAR; fatal: BOOLEAN);
  362.   VAR
  363.     lidummy: LONGINT;
  364.   BEGIN
  365.     IF stdErr#NIL THEN
  366.       IF fatal THEN
  367.         lidummy:=DosL.Write(stdErr,SYSTEM.ADR("FATAL ERROR "),12);
  368.         lidummy:=DosL.Write(stdErr,SYSTEM.ADR(x1),String.Length(x1));
  369.         lidummy:=DosL.Write(stdErr,SYSTEM.ADR("\n\o"),1);
  370.         lidummy:=DosL.Write(stdErr,SYSTEM.ADR(x2),String.Length(x2));
  371.         lidummy:=DosL.Write(stdErr,SYSTEM.ADR("\n\o"),1);
  372.         lidummy:=DosL.Write(stdErr,SYSTEM.ADR(x3),String.Length(x3));
  373.         lidummy:=DosL.Write(stdErr,SYSTEM.ADR("\n\n"),2);
  374.         Arts.Exit(DosD.fail);
  375.       ELSIF (NOT args.quiet) OR ((x1[0]="F") & (x1[1]="A") & (x1[2]="T")) THEN
  376.         lidummy:=DosL.Write(stdErr,SYSTEM.ADR(x1),String.Length(x1));
  377.         lidummy:=DosL.Write(stdErr,SYSTEM.ADR("\n\o"),1);
  378.         lidummy:=DosL.Write(stdErr,SYSTEM.ADR(x2),String.Length(x2));
  379.         lidummy:=DosL.Write(stdErr,SYSTEM.ADR("\n\o"),1);
  380.         lidummy:=DosL.Write(stdErr,SYSTEM.ADR(x3),String.Length(x3));
  381.         lidummy:=DosL.Write(stdErr,SYSTEM.ADR("\n\n"),2);
  382.       END;
  383.     ELSE
  384.       IF fatal THEN
  385.         InOut.WriteString("FATAL ERROR ");
  386.         InOut.WriteString(x1); InOut.WriteLn;
  387.         InOut.WriteString(x2); InOut.WriteLn;
  388.         InOut.WriteString(x3); InOut.WriteLn;
  389.         InOut.WriteLn;
  390.         Arts.Exit(DosD.fail);
  391.       ELSIF (NOT args.quiet) OR ((x1[0]="F") & (x1[1]="A") & (x1[2]="T")) THEN
  392.         InOut.WriteString(x1); InOut.WriteLn;
  393.         InOut.WriteString(x2); InOut.WriteLn;
  394.         InOut.WriteString(x3); InOut.WriteLn;
  395.         InOut.WriteLn;
  396.       END;
  397.     END;
  398.   END MyError;
  399.  
  400.  
  401.  
  402. (*---------------------------------------------------------------------------
  403.   GetHead: Remove first char from a string, return it as result
  404.   ---------------------------------------------------------------------------*)
  405. PROCEDURE GetHead(VAR s: ARRAY OF CHAR): CHAR;
  406.   VAR
  407.     c: CHAR;
  408.   BEGIN
  409.     c:=s[0];
  410.     String.DeleteChar(s,0);
  411.     RETURN c;
  412.   END GetHead;
  413.  
  414.  
  415.  
  416. (*---------------------------------------------------------------------------
  417.   KillSpaces: Remove leading spaces from a string
  418.   ---------------------------------------------------------------------------*)
  419. PROCEDURE KillSpaces(VAR l1: StringT);
  420.   VAR
  421.     c: CHAR;
  422.   BEGIN
  423.     WHILE l1[0]=" " DO
  424.       c:=GetHead(l1);
  425.       Break.TestBreak();
  426.     END;
  427.   END KillSpaces;
  428.  
  429.  
  430.  
  431. (*---------------------------------------------------------------------------
  432.   Get: Remove first "word" from a string. "Word" means "a sequence of
  433.        non-spaces, terminated by at least one space". Return this word
  434.        as a separate string
  435.   ---------------------------------------------------------------------------*)
  436. PROCEDURE Get(VAR s1,s2: StringT): BOOLEAN;
  437.   VAR
  438.     c: CHAR;
  439.   BEGIN
  440.     s2[0]:=ASCII.nul;
  441.     KillSpaces(s1);
  442.     c:=GetHead(s1);
  443.     WHILE (c#" ") AND (c#ASCII.nul) DO
  444.       Break.TestBreak();
  445.       String.ConcatChar(s2,c);
  446.       c:=GetHead(s1);
  447.     END;
  448.     RETURN String.Length(s2)>0;
  449.   END Get;
  450.  
  451.  
  452.  
  453. (*---------------------------------------------------------------------------
  454.   ConvDate: Convert ASCII representations of date and time to Dos format
  455.   ---------------------------------------------------------------------------*)
  456. (*$ CopyDyn:=FALSE *)
  457. PROCEDURE ConvDate(ds,ts: ARRAY OF CHAR; VAR dat: DosD.Date): BOOLEAN;
  458.   VAR
  459.     dt: DosD.DateTime;
  460.     res: LONGINT;
  461.   BEGIN
  462.     dt.format:=DosD.formatDOS;
  463.     dt.flags:=DosD.DateTimeFlagSet{};
  464.     dt.strDate:=SYSTEM.ADR(ds);
  465.     dt.strTime:=SYSTEM.ADR(ts);
  466.     res:=DosL.StrToDate(SYSTEM.ADR(dt));
  467.     dat:=dt.date;
  468.     RETURN res=0;
  469.   END ConvDate;
  470.  
  471.  
  472.  
  473. (*---------------------------------------------------------------------------
  474.   ParseDosArgs: Parse argument string and set "args" record accordingly
  475.   ---------------------------------------------------------------------------*)
  476. PROCEDURE ParseDosArgs(VAR s: StringT);
  477.   VAR
  478.     ts: StringT;
  479.     dargs: DosArgs;
  480.     rc: SYSTEM.ADDRESS;
  481.     pres: LONGINT;
  482.   BEGIN
  483.     rdargsPtr:=DosL.AllocDosObject(DosD.dosRdArgs,NIL);
  484.     IF rdargsPtr=NIL THEN
  485.       MyError("in command line parser",
  486.               "not enough memory",
  487.               "can't parse command line arguments",TRUE);
  488.     END;
  489.  
  490.     rdargsPtr^.source.buffer:=SYSTEM.ADR(s);
  491.     rdargsPtr^.source.length:=String.Length(s);
  492.     rdargsPtr^.source.curChr:=0;
  493.     rdargsPtr^.daList:=0;
  494.     rdargsPtr^.buffer:=NIL;
  495.     rdargsPtr^.bufSiz:=0;
  496.     rdargsPtr^.extHelp:=SYSTEM.ADR(extendedhelp);
  497.     rdargsPtr^.flags:=DosD.RDAFlagSet{};
  498.  
  499.     IF DosL.ReadArgs(SYSTEM.ADR(argTemplate),SYSTEM.ADR(dargs),rdargsPtr)=NIL THEN
  500.       MyError("in command line parser","illegal command line",s,TRUE);
  501.     END;
  502.  
  503.     IF dargs.file#NIL THEN
  504.       String.Copy(args.xferstat,dargs.file^);
  505.     END;
  506.  
  507.     IF dargs.data#NIL THEN
  508.       String.Copy(args.xstatdata,dargs.data^);
  509.     END;
  510.  
  511.     args.verbose:=(dargs.verb#0);
  512.  
  513.     IF dargs.fdate#NIL THEN
  514.       IF ConvDate(dargs.fdate^,"00:00:00",args.from) THEN
  515.         MyError("in command line","illegal FDATE",dargs.fdate^,TRUE);
  516.       END;
  517.     END;
  518.  
  519.     IF dargs.tdate#NIL THEN
  520.       IF ConvDate(dargs.tdate^,"23:59:59",args.to) THEN
  521.         MyError("in command line","illegal TDATE",dargs.tdate^,TRUE);
  522.       END;
  523.     END;
  524.  
  525.     IF dargs.host#NIL THEN
  526.       pres:=DosL.ParsePatternNoCase(dargs.host,
  527.                                     SYSTEM.ADR(args.host),
  528.                                     SIZE(args.host)-2); (* just being cautious :-) *)
  529.       IF pres<0 THEN
  530.         MyError("in command line","pattern in HOST too complex",dargs.host^,TRUE);
  531.       END;
  532.       args.perhost:=(pres=1);
  533.     END;
  534.  
  535.     IF args.in THEN
  536.       args.in:=(dargs.noin=0);
  537.     END;
  538.  
  539.     IF args.out THEN
  540.       args.out:=(dargs.noout=0);
  541.     END;
  542.  
  543.     args.quiet:=args.quiet OR (dargs.quiet#0);
  544.  
  545.     args.stderr:=(dargs.stderr#0);
  546.  
  547.     IF dargs.month#NIL THEN
  548.       ts:="01-";
  549.       String.Concat(ts,dargs.month^);
  550.       IF ConvDate(ts,"00:00:00",args.from) THEN
  551.         MyError("in command line","illegal MONTH",dargs.month^,TRUE);
  552.       END;
  553.  
  554.       ts:="31-";
  555.       String.Concat(ts,dargs.month^);
  556.       IF ConvDate(ts,"23:59:59",args.to) THEN
  557.         ts:="30-";
  558.         String.Concat(ts,dargs.month^);
  559.         IF ConvDate(ts,"23:59:59",args.to) THEN
  560.           ts:="29-";
  561.           String.Concat(ts,dargs.month^);
  562.           IF ConvDate(ts,"23:59:59",args.to) THEN
  563.             ts:="28-";
  564.             String.Concat(ts,dargs.month^);
  565.             IF ConvDate(ts,"23:59:59",args.to) THEN
  566.               MyError("in command line","illegal MONTH",dargs.month^,TRUE);
  567.             END;
  568.           END;
  569.         END;
  570.       END;
  571.  
  572.       IF (DosL.dosVersion<40) THEN
  573.         MyError("WARNING: Due to a bug in the Kickstart version you're using, the MONTHLY",
  574.                 "         option will yield unexpected results under certain conditions.",
  575.                 "         You should probably use FROMDATE and PERIOD instead.",
  576.                 FALSE);
  577.       END;
  578.       IF (DosL.dosVersion=40) THEN
  579.         MyError("WARNING: Some revisions of your Kickstart may still have a bug which might",
  580.                 "         cause the MONTHLY option to yield unexpected results under certain",
  581.                 "         conditions. You should probably use FROMDATE and PERIOD instead.",
  582.                 FALSE);
  583.       END;
  584.  
  585.       args.monthly:=TRUE;
  586.     END;
  587.  
  588.     IF (dargs.period#NIL) & (dargs.last#NIL) & (dargs.period^#0) & (dargs.last^#0) THEN
  589.       MyError("in command line","PREVIOUS and LAST specified simultaneously",s,TRUE);
  590.     END;
  591.  
  592.     IF dargs.period#NIL THEN
  593.       args.to:=args.from;
  594.       args.to.days:=args.to.days+dargs.period^;
  595.     END;
  596.  
  597.     IF dargs.last#NIL THEN
  598.       args.from:=args.to;
  599.       args.from.days:=args.from.days-dargs.last^;
  600.     END;
  601.  
  602.     IF rdargsPtr#NIL THEN
  603.       DosL.FreeArgs(rdargsPtr);
  604.       DosL.FreeDosObject(DosD.dosRdArgs,rdargsPtr);
  605.       rdargsPtr:=NIL;
  606.     END;
  607.   END ParseDosArgs;
  608.  
  609.  
  610.  
  611. (*---------------------------------------------------------------------------
  612.   PresetArgs: Set default values for "args" record
  613.   ---------------------------------------------------------------------------*)
  614. PROCEDURE PresetArgs();
  615.   BEGIN
  616.     args.xstatdata:=defxstatdata;
  617.     args.xferstat:=defxferstat;
  618.     args.verbose:=FALSE;
  619.     args.from.days:=0;   (* "01-JAN-78" "00:00:00" *)
  620.     args.from.minute:=0; (* "01-JAN-78" "00:00:00" *)
  621.     args.from.tick:=0;   (* "01-JAN-78" "00:00:00" *)
  622.     DosL.DateStamp(SYSTEM.ADR(args.to));
  623.     args.host[0]:=ASCII.nul;
  624.     args.in:=TRUE;
  625.     args.out:=TRUE;
  626.     args.quiet:=FALSE;
  627.     args.monthly:=FALSE;
  628.   END PresetArgs;
  629.  
  630.  
  631.  
  632. (*---------------------------------------------------------------------------
  633.   GetLine: Read a line from the input file
  634.   ---------------------------------------------------------------------------*)
  635. PROCEDURE GetLine(VAR line: StringT);
  636.   VAR
  637.     res: LONGINT;
  638.   BEGIN
  639.     IF inf.fh#NIL THEN
  640.       res:=DosL.FGets(inf.fh,SYSTEM.ADR(line),strlen);
  641.       inf.eof:=(res=0) AND (DosL.IoErr()=0);
  642.     END;
  643.     IF (String.Length(line)>0) AND (line[String.Length(line)-1]=ASCII.lf) THEN
  644.       line[String.Length(line)-1]:="\o";
  645.     END;
  646.   END GetLine;
  647.  
  648.  
  649.  
  650. (*---------------------------------------------------------------------------
  651.   GetLog: Read a two-line log entry from Xferstat
  652.   ---------------------------------------------------------------------------*)
  653. PROCEDURE GetLog(VAR l1,l2: StringT);
  654.   BEGIN
  655.     l1[0]:="\o";
  656.     l2[0]:="\o";
  657.     REPEAT
  658.       Break.TestBreak();
  659.       GetLine(l1);
  660.     UNTIL (inf.eof) OR (l1[0]="<") OR (l1[0]=">");
  661.     IF NOT inf.eof THEN
  662.       GetLine(l2);
  663.     ELSE
  664.       l1[0]:="\o";
  665.     END;
  666.   END GetLog;
  667.  
  668.  
  669.  
  670. (*---------------------------------------------------------------------------
  671.   Skip: Skip the first "word" of a string; refer to "Get()" for an
  672.         explanation ot the term "word"
  673.   ---------------------------------------------------------------------------*)
  674. PROCEDURE Skip(VAR s: StringT);
  675.   VAR
  676.     dum: BOOLEAN;
  677.     x: StringT;
  678.   BEGIN
  679.     dum:=Get(s,x);
  680.   END Skip;
  681.  
  682.  
  683.  
  684. (*---------------------------------------------------------------------------
  685.   GetLNum: Get the first "word" of a string, and convert it to a LONGINT
  686.            (if possible). Refer to "Get()" for an explanation of the
  687.            term "word"
  688.   ---------------------------------------------------------------------------*)
  689. PROCEDURE GetLNum(VAR s: StringT; VAR x: LONGINT): BOOLEAN;
  690.   VAR
  691.     sn: StringT;
  692.     res: LONGINT;
  693.   BEGIN
  694.     IF Get(s,sn) THEN
  695.       res:=DosL.StrToLong(SYSTEM.ADR(sn),x);
  696.       RETURN res>0;
  697.     END;
  698.     RETURN FALSE;
  699.   END GetLNum;
  700.  
  701.  
  702.  
  703. (*---------------------------------------------------------------------------
  704.   GetNum: Call "GetLNum()" and make sure the result is an INTEGER
  705.   ---------------------------------------------------------------------------*)
  706. PROCEDURE GetNum(VAR s: StringT; VAR x: INTEGER): BOOLEAN;
  707.   VAR
  708.     l: LONGINT;
  709.   BEGIN
  710.     IF GetLNum(s,l) THEN
  711.       IF l>MAX(INTEGER) THEN
  712.         x:=0;
  713.       ELSE
  714.         x:=INTEGER(l);
  715.         RETURN TRUE;
  716.       END;
  717.     ELSE
  718.       x:=0;
  719.     END;
  720.     RETURN FALSE;
  721.   END GetNum;
  722.  
  723.  
  724.  
  725. (*---------------------------------------------------------------------------
  726.   GetReal: Get the first "word" of a string, and convert it to a REAL
  727.            (if possible). Refer to "Get()" for an explanation of the
  728.            term "word"
  729.   ---------------------------------------------------------------------------*)
  730. PROCEDURE GetReal(VAR s: StringT; VAR x: REAL): BOOLEAN;
  731.   VAR
  732.     sn: StringT;
  733.     err: BOOLEAN;
  734.   BEGIN
  735.     IF Get(s,sn) THEN
  736.       RealConversions.StrToReal(sn,x,err);
  737.       RETURN NOT err;
  738.     END;
  739.     RETURN FALSE;
  740.   END GetReal;
  741.  
  742.  
  743.  
  744. (*---------------------------------------------------------------------------
  745.   GetTime: Get the first two "words" of a string, and convert them to
  746.            Dos format date and time (if possible). Refer to "Get()" for an
  747.            explanation of the term "word"
  748.   ---------------------------------------------------------------------------*)
  749. PROCEDURE GetTime(VAR s: StringT; VAR t: LONGINT; VAR dos: DosD.Date): BOOLEAN;
  750.   VAR
  751.     ds,ts: StringT;
  752.     dt: DosD.DateTime;
  753.   BEGIN
  754.     IF Get(s,ds) THEN
  755.       IF Get(s,ts) THEN
  756.         dt.format:=DosD.formatCDN;
  757.         dt.flags:=DosD.DateTimeFlagSet{};
  758.         dt.strDate:=SYSTEM.ADR(ds);
  759.         dt.strTime:=SYSTEM.ADR(ts);
  760.  
  761.         IF DosL.StrToDate(SYSTEM.ADR(dt))#0 THEN
  762.           dos:=dt.date;
  763.           t:=(dos.days MOD 7)*dmult+(dos.minute*mmult)+(dos.tick DIV 50);
  764.           RETURN TRUE;
  765.         END;
  766.       END;
  767.     END;
  768.     RETURN FALSE;
  769.   END GetTime;
  770.  
  771.  
  772.  
  773. (*---------------------------------------------------------------------------
  774.   TestXStatHeader: Check if the input file's header is "H XSTAT DATA"
  775.   ---------------------------------------------------------------------------*)
  776. PROCEDURE TestXStatHeader();
  777.  VAR
  778.   s1,s,t: StringT;
  779.   c: CHAR;
  780.  BEGIN
  781.   GetLine(s);
  782.   s1:=s;
  783.   c:=GetHead(s);
  784.   IF c="H" THEN
  785.    IF Get(s,t) OR (String.Compare(t,"XSTAT")#0) THEN
  786.     IF NOT (Get(s,t) OR (String.Compare(t,"DATA")#0)) THEN
  787.      MyError("in XStat.data header",
  788.              s1,"keyword DATA not found",TRUE);
  789.     END;
  790.    ELSE
  791.     MyError("in XStat.data header",
  792.             s1,"keyword XSTAT not found",TRUE);
  793.    END;
  794.   ELSE
  795.    MyError("in XStat.data header",
  796.            s1,"header identifier H not found",TRUE);
  797.   END;
  798.  END TestXStatHeader;
  799.  
  800.  
  801.  
  802. (*---------------------------------------------------------------------------
  803.   ParseXSDataNEntry: Parse a "host name" line from XStat.data
  804.   ---------------------------------------------------------------------------*)
  805. PROCEDURE ParseXSDataNEntry(VAR s,s1: StringT; VAR ccxsd: XSDataPtr);
  806.   VAR
  807.     cxsd: XSDataPtr;
  808.     xsd: XSData;
  809.     t: StringT;
  810.     first: BOOLEAN;
  811.   BEGIN
  812.     IF (ccxsd#NIL) AND (ccxsd^.costlist=NIL) AND (NOT ccxsd^.local) AND (NOT ccxsd^.ignore) THEN
  813. (* -------- obsolete --------
  814.       MyError("in XStat.data",
  815.               "N-line without subsequent C-lines for",
  816.               ccxsd^.host,TRUE);
  817.     END;
  818.    -------------------------- *)
  819.       first:=FALSE;
  820.     ELSE
  821.       first:=TRUE;
  822.     END;
  823.  
  824.     IF Get(s,t) THEN
  825.       REPEAT
  826.         xsd.next:=NIL;
  827.         xsd.host:=t;
  828.         xsd.costlist:=NIL;
  829.         xsd.offset:=0;
  830.         xsd.local:=FALSE;
  831.         xsd.ignore:=FALSE;
  832.         xsd.double:=FALSE;
  833.         cxsd:=xsroot;
  834.         IF xsroot#NIL THEN
  835.           IF String.Compare(xsd.host,cxsd^.host)=0 THEN
  836.             MyError("in XStat.data",
  837.                     "host name already used for",
  838.                     xsd.host,TRUE);
  839.           END;
  840.           WHILE cxsd^.next#NIL DO
  841.             IF String.Compare(xsd.host,cxsd^.next^.host)=0 THEN
  842.               MyError("in XStat.data",
  843.                       "host name already used for",
  844.                       xsd.host,TRUE);
  845.             END;
  846.             Break.TestBreak();
  847.             cxsd:=cxsd^.next;
  848.           END;
  849.           cxsd^.next:=ExecL.AllocMem(SIZE(xsd),ExecD.MemReqSet{});
  850.           IF cxsd^.next=NIL THEN
  851.             MyError("","not enough memory","",TRUE);
  852.           END;
  853.           cxsd:=cxsd^.next;
  854.           IF first THEN
  855.             first:=FALSE;
  856.             ccxsd:=cxsd;
  857.           ELSE
  858.             xsd.double:=TRUE;
  859.           END;
  860.         ELSE
  861.           xsroot:=ExecL.AllocMem(SIZE(xsd),ExecD.MemReqSet{});
  862.           IF xsroot=NIL THEN
  863.             MyError("","not enough memory","",TRUE);
  864.           END;
  865.           cxsd:=xsroot;
  866.           ccxsd:=cxsd;
  867.           first:=FALSE;
  868.         END;
  869.         cxsd^:=xsd;
  870.       UNTIL NOT Get(s,t);
  871.     ELSE
  872.       MyError("in XStat.data",
  873.               s1,"no host name found in N-line",TRUE);
  874.     END;
  875.   END ParseXSDataNEntry;
  876.  
  877.  
  878.  
  879. (*---------------------------------------------------------------------------
  880.   ParseXSDataCEntry: Parse a "cost info" line from XStat.data
  881.   ---------------------------------------------------------------------------*)
  882. PROCEDURE ParseXSDataCEntry(VAR s,s1: StringT; VAR cxsd: XSDataPtr);
  883.   VAR
  884.     t,t1: StringT;
  885.     cost: Cost;
  886.     ccp: CostPtr;
  887.     temp: INTEGER;
  888.     c0{R.D7},c1{R.D6}: CHAR;
  889.   BEGIN
  890.     IF cxsd=NIL THEN
  891.       MyError("in XStat.data",s1,"C-line without corresponding N-line",TRUE);
  892.     END;
  893.  
  894.     cost.next:=NIL;
  895.     cost.stop:=7*dmult-1;
  896.  
  897.     IF NOT Get(s,t) THEN
  898.       MyError("in XStat.data",s1,"illegal or missing start time in C-line",TRUE);
  899.     END;
  900.  
  901.     IF String.Compare(t,"LOCAL")=0 THEN
  902.       IF cxsd^.costlist#NIL THEN
  903.         MyError("in XStat.data",s1,"LOCAL must be the only C-line for the host",TRUE);
  904.       ELSIF cxsd^.local#FALSE THEN
  905.         MyError("in XStat.data",s1,"duplicate LOCAL after N-line",TRUE);
  906.       ELSIF cxsd^.ignore#FALSE THEN
  907.         MyError("in XStat.data",s1,"LOCAL and IGNORE are mutually exclusive",TRUE);
  908.       ELSE
  909.         cxsd^.local:=TRUE;
  910.       END;
  911.  
  912.     ELSIF String.Compare(t,"IGNORE")=0 THEN
  913.       IF cxsd^.costlist#NIL THEN
  914.         MyError("in XStat.data",s1,"IGNORE must be the only C-line for the host",TRUE);
  915.       ELSIF cxsd^.ignore#FALSE THEN
  916.         MyError("in XStat.data",s1,"duplicate IGNORE after N-line",TRUE);
  917.       ELSIF cxsd^.local#FALSE THEN
  918.         MyError("in XStat.data",s1,"LOCAL and IGNORE are mutually exclusive",TRUE);
  919.       ELSE
  920.         cxsd^.ignore:=TRUE;
  921.       END;
  922.  
  923.     ELSIF (t[2]="-") AND (t[5]=":") AND (t[8]=":") AND (String.Length(t)=11) THEN
  924.       c0:=t[0]; c1:=t[1];
  925.       IF (c0="S") AND (c1="U") THEN
  926.         cost.start:=dsun;
  927.       ELSIF (c0="M") AND (c1="O") THEN
  928.         cost.start:=dmon;
  929.       ELSIF (c0="T") AND (c1="U") THEN
  930.         cost.start:=dtue;
  931.       ELSIF (c0="W") AND (c1="E") THEN
  932.         cost.start:=dwed;
  933.       ELSIF (c0="T") AND (c1="H") THEN
  934.         cost.start:=dthu;
  935.       ELSIF (c0="F") AND (c1="R") THEN
  936.         cost.start:=dfri;
  937.       ELSIF (c0="S") AND (c1="A") THEN
  938.         cost.start:=dsat;
  939.       ELSE
  940.         MyError("in XStat.data",s1,"illegal day of week in start time in C-line",TRUE);
  941.       END;
  942.  
  943.       t[0]:=" "; t[1]:=" "; t[2]:=" "; t[5]:=" "; t[8]:=" ";
  944.  
  945.       IF NOT GetNum(t,temp) THEN
  946.         MyError("in XStat.data",s1,"illegal 'hours' part in start time in C-line",TRUE);
  947.       END;
  948.       cost.start:=cost.start+hmult*LONGINT(temp);
  949.  
  950.       IF NOT GetNum(t,temp) THEN
  951.         MyError("in XStat.data",s1,"illegal 'minutes' part in start time in C-line",TRUE);
  952.       END;
  953.       cost.start:=cost.start+mmult*LONGINT(temp);
  954.  
  955.       IF NOT GetNum(t,temp) THEN
  956.         MyError("in XStat.data",s1,"illegal 'seconds' part in start time in C-line",TRUE);
  957.       END;
  958.       cost.start:=cost.start+smult*LONGINT(temp);
  959.  
  960.       IF NOT GetReal(s,cost.unit) THEN
  961.         MyError("in XStat.data",s1,"illegal 'seconds per unit' in C-line",TRUE);
  962.       END;
  963.  
  964.       IF NOT GetReal(s,cost.mpu) THEN
  965.         MyError("in XStat.data",s1,"illegal 'money per unit' in C-line",TRUE);
  966.       END;
  967.  
  968.       IF (cxsd^.local) OR (cxsd^.ignore) THEN
  969.         MyError("in XStat.data",s1,"no more C-lines allowed after LOCAL or IGNORE",TRUE);
  970.       END;
  971.  
  972.       ccp:=cxsd^.costlist;
  973.       IF ccp#NIL THEN
  974.         WHILE ccp^.next#NIL DO
  975.           Break.TestBreak();
  976.           ccp:=ccp^.next;
  977.         END;
  978.         ccp^.next:=ExecL.AllocMem(SIZE(Cost),ExecD.MemReqSet{});
  979.         IF ccp^.next=NIL THEN
  980.           MyError("","not enough memory","",TRUE);
  981.         END;
  982.         ccp^.next^:=cost;
  983.         ccp^.stop:=cost.start-1;
  984.         IF ccp^.stop<ccp^.start THEN
  985.           MyError("in XStat.data","connect start time sequence error",
  986.                   "(you probably swapped some C-lines, or you left out an N-line)",TRUE);
  987.         END;
  988.       ELSE
  989.         cxsd^.costlist:=ExecL.AllocMem(SIZE(Cost),ExecD.MemReqSet{});
  990.         IF cxsd^.costlist=NIL THEN
  991.           MyError("","not enough memory","",TRUE);
  992.         END;
  993.         cxsd^.costlist^:=cost;
  994.         IF cost.start#0 THEN
  995.           MyError("in XStat.data",s1,"start time in first C-line must be SU-00:00:00",TRUE);
  996.         END;
  997.       END;
  998.     ELSE
  999.       MyError("in XStat.data",s1,"illegal or missing start time in C-line",TRUE);
  1000.     END;
  1001.  
  1002.     (* cxsd will always be #NIL here... *)
  1003.     WHILE cxsd^.next#NIL DO
  1004.       (* copy costlistPtr to next^.costlistPtr *)
  1005.       cxsd^.next^.costlist:=cxsd^.costlist;
  1006.       cxsd^.next^.local:=cxsd^.local;
  1007.       cxsd^.next^.ignore:=cxsd^.ignore;
  1008.       (* advance cxsd to cxsd#.next; note: cxsd is a VAR parameter, so this *)
  1009.       (* will only happen for the first C-record found... from then on,     *)
  1010.       (* this costlistPtr copying will not be done again.                   *)
  1011.       cxsd:=cxsd^.next;
  1012.     END;
  1013.   END ParseXSDataCEntry;
  1014.  
  1015.  
  1016.  
  1017. (*---------------------------------------------------------------------------
  1018.   ParseXSDataSEntry: Parse a "currency sign info" line from XStat.data
  1019.   ---------------------------------------------------------------------------*)
  1020. PROCEDURE ParseXSDataSEntry(VAR s,s1: StringT; VAR cxsd: XSDataPtr);
  1021.   VAR
  1022.     t: StringT;
  1023.   BEGIN
  1024.     IF (cxsd#NIL) THEN
  1025.       MyError("in XStat.data",
  1026.               s1,"S-line after the first connection cost record",TRUE);
  1027.     END;
  1028.  
  1029.     IF Get(s,t) THEN
  1030.       IF currency[0]=ASCII.nul THEN
  1031.         currency:=t;
  1032.         KillSpaces(currency);
  1033.       ELSE
  1034.         MyError("in XStat.data",
  1035.                 s1,"duplicate S-line found",TRUE);
  1036.       END;
  1037.     ELSE
  1038.       MyError("in XStat.data",
  1039.               s1,"missing currency sign in S-line",TRUE);
  1040.     END;
  1041.   END ParseXSDataSEntry;
  1042.  
  1043.  
  1044.  
  1045. (*---------------------------------------------------------------------------
  1046.   ParseXSDataOEntry: Parse a "currency sign info" line from XStat.data
  1047.   ---------------------------------------------------------------------------*)
  1048. PROCEDURE ParseXSDataOEntry(VAR s,s1: StringT; VAR cxsd: XSDataPtr);
  1049.   VAR
  1050.     t: LONGINT;
  1051.   BEGIN
  1052.     IF (cxsd=NIL) THEN
  1053.       MyError("in XStat.data",
  1054.               s1,"O-line before the first connection cost record",TRUE);
  1055.     END;
  1056.  
  1057.     IF (cxsd^.costlist#NIL) THEN
  1058.       MyError("in XStat.data",
  1059.               s1,"O-line not directly after a N-line",TRUE);
  1060.     END;
  1061.  
  1062.     IF GetLNum(s,t) THEN
  1063.       IF cxsd^.offset=0 THEN
  1064.         cxsd^.offset:=t;
  1065.       ELSE
  1066.         MyError("in XStat.data",
  1067.                 s1,"duplicate O-line found",TRUE);
  1068.       END;
  1069.     ELSE
  1070.       MyError("in XStat.data",
  1071.               s1,"missing offset in O-line",TRUE);
  1072.     END;
  1073.   END ParseXSDataOEntry;
  1074.  
  1075.  
  1076.  
  1077. (*---------------------------------------------------------------------------
  1078.   GetXStatData: Read and parse the XStat.data file
  1079.   ---------------------------------------------------------------------------*)
  1080. PROCEDURE GetXStatData();
  1081.  VAR
  1082.   s1,s: StringT;
  1083.   c: CHAR;
  1084.   cxsd: XSDataPtr;
  1085.  BEGIN
  1086.   cxsd:=NIL;
  1087.   TestXStatHeader();
  1088.   REPEAT
  1089.    Break.TestBreak();
  1090.    GetLine(s);
  1091.    s1:=s;
  1092.    c:=GetHead(s);
  1093.    CASE c OF
  1094.     |"#": (* NOP *)
  1095.     |"N": ParseXSDataNEntry(s,s1,cxsd);
  1096.     |"O": ParseXSDataOEntry(s,s1,cxsd);
  1097.     |"C": ParseXSDataCEntry(s,s1,cxsd);
  1098.     |"S": ParseXSDataSEntry(s,s1,cxsd);
  1099.     |ELSE (* unknown: NOP *)
  1100.    END;
  1101.   UNTIL inf.eof;
  1102.  END GetXStatData;
  1103.  
  1104.  
  1105.  
  1106. (*---------------------------------------------------------------------------
  1107.   ParseLog: Parse a two-line connection data entry from Xferstat
  1108.   ---------------------------------------------------------------------------*)
  1109. (*$ CopyDyn:=FALSE *)
  1110. PROCEDURE ParseLog(line1,line2: StringT; VAR log: Connect): BOOLEAN;
  1111.  VAR
  1112.   c: CHAR;
  1113.   l1,l2: StringT;
  1114.   dummy: DosD.Date;
  1115.   i: INTEGER;
  1116.  BEGIN
  1117.   l1:=line1;
  1118.   l2:=line2;
  1119.   c:=GetHead(l1);
  1120.   log.callout:=(c="<");
  1121.  
  1122.   IF Get(l1,log.host) THEN
  1123. (* ---------------------------------------------------
  1124.    IF (log.host[0]>="0") AND (log.host[0]<="9") THEN
  1125.     MyError("invalid log entry: can't find host name",line1,line2,FALSE);
  1126.     RETURN FALSE;
  1127.    END;
  1128.    --------------------------------------------------- *)
  1129.    IF line1[2]=" " THEN
  1130.      log.host:=".illegal.";
  1131.      MyError("invalid log entry: can't find host name (substituting .illegal.)",line1,line2,FALSE);
  1132.      l1:=line1;
  1133.      c:=GetHead(l1);
  1134.    END;
  1135.    IF GetTime(l1,log.start,log.sdate) THEN
  1136.     Skip(l1);
  1137.     IF GetTime(l1,log.stop,dummy) THEN
  1138.      c:=GetHead(l2);
  1139.      IF c="|" THEN
  1140.       FOR i:=1 TO 9 DO
  1141.         c:=GetHead(l2); (* skip the initial "|" and the next 9 chars *)
  1142.       END;
  1143.       IF GetLNum(l2,log.inbb) THEN
  1144.        IF GetLNum(l2,log.outbb) THEN
  1145.         Skip(l2);
  1146.         IF GetLNum(l2,log.inbn) THEN
  1147.          IF GetLNum(l2,log.outbn) THEN
  1148.           RETURN TRUE;
  1149.          ELSE
  1150.           MyError("invalid log entry: can't get net send bytes",line1,line2,FALSE);
  1151.          END;
  1152.         ELSE
  1153.          MyError("invalid log entry: can't get net receive bytes",line1,line2,FALSE);
  1154.         END;
  1155.        ELSE
  1156.         MyError("invalid log entry: can't get gross send bytes",line1,line2,FALSE);
  1157.        END;
  1158.       ELSE
  1159.        MyError("invalid log entry: can't get gross receive bytes",line1,line2,FALSE);
  1160.       END;
  1161.      ELSE
  1162.       MyError("invalid log entry: illegal continuation line header ",line1,line2,FALSE);
  1163.      END;
  1164.     ELSE
  1165.      MyError("invalid log entry: can't compute end time",line1,line2,FALSE);
  1166.     END;
  1167.    ELSE
  1168.     MyError("invalid log entry: can't compute start time",line1,line2,FALSE);
  1169.    END;
  1170.   ELSE
  1171.    MyError("invalid log entry: can't find host name",line1,line2,FALSE);
  1172.   END;
  1173.   RETURN FALSE;
  1174.  END ParseLog;
  1175.  
  1176.  
  1177.  
  1178. (*---------------------------------------------------------------------------
  1179.   ComputeCost: Compute the cost for a single connect
  1180.   ---------------------------------------------------------------------------*)
  1181. PROCEDURE ComputeCost(VAR log: Connect): BOOLEAN;
  1182.   VAR
  1183.     cxsd: XSDataPtr;
  1184.     ccost: CostPtr;
  1185.     time: REAL;
  1186.     flatunits: LONGINT;
  1187.   BEGIN
  1188.     log.seconds:=log.stop-log.start+1;
  1189.     IF log.seconds<0 THEN
  1190.       log.seconds:=log.seconds+7*dmult;
  1191.     END;
  1192.  
  1193.     cxsd:=xsroot;
  1194.     WHILE (cxsd#NIL) AND (String.Compare(cxsd^.host,log.host)#0) DO
  1195.       Break.TestBreak();
  1196.       cxsd:=cxsd^.next;
  1197.     END;
  1198.     IF cxsd=NIL THEN
  1199.       cxsd:=xsroot;
  1200.       WHILE (cxsd#NIL) AND (String.Compare(cxsd^.host,".default.")#0) DO
  1201.         Break.TestBreak();
  1202.         cxsd:=cxsd^.next;
  1203.       END;
  1204.       IF cxsd=NIL THEN
  1205.         MyError("ERROR: no connection cost data found for host",log.host,
  1206.                 "extend your XStat.data file or use '.default.'!",FALSE);
  1207.         RETURN FALSE;
  1208.       END;
  1209.     END;
  1210.  
  1211.     log.start:=log.start-cxsd^.offset;
  1212.     log.seconds:=log.seconds+cxsd^.offset;
  1213.  
  1214.     IF NOT (cxsd^.local OR cxsd^.ignore) THEN
  1215.       ccost:=cxsd^.costlist;
  1216.       WHILE log.start>ccost^.stop DO
  1217.         Break.TestBreak();
  1218.         ccost:=ccost^.next;
  1219.       END;
  1220.     END;
  1221.  
  1222.     IF log.stop<log.start THEN
  1223.       log.stop:=log.stop+7*dmult;
  1224.     END;
  1225.     log.cost:=0.0;
  1226.     log.units:=0;
  1227.  
  1228.     IF NOT (cxsd^.local OR cxsd^.ignore) THEN
  1229.       time:=REAL(log.start);
  1230.       REPEAT
  1231.         Break.TestBreak();
  1232.         log.cost:=log.cost+ccost^.mpu;
  1233.         IF ccost^.unit=0.0 THEN
  1234.           (* flat rate *)
  1235.           flatunits:=1;
  1236.           IF log.stop>ccost^.stop THEN
  1237.             time:=REAL(ccost^.stop)+1.0;
  1238.           ELSE
  1239.             time:=REAL(log.stop)+1.0;
  1240.           END;
  1241.         ELSE
  1242.           time:=time+ccost^.unit;
  1243.           INC(log.units);
  1244.           IF flatunits#0 THEN
  1245.             INC(log.units);
  1246.             flatunits:=0;
  1247.           END;
  1248.         END;
  1249.         IF flatunits#0 THEN
  1250.           INC(log.units);
  1251.           flatunits:=0;
  1252.         END;
  1253.  
  1254.         IF LONGINT(time)>ccost^.stop THEN
  1255.           ccost:=ccost^.next;
  1256.           IF ccost=NIL THEN
  1257.             ccost:=cxsd^.costlist;
  1258.             time:=time-REAL(7*dmult);
  1259.             log.stop:=log.stop-7*dmult;
  1260.           END;
  1261.         END;
  1262.       UNTIL LONGINT(time)>log.stop;
  1263.     END;
  1264.  
  1265.     log.local:=cxsd^.local;
  1266.     log.ignore:=cxsd^.ignore;
  1267.     RETURN TRUE;
  1268.   END ComputeCost;
  1269.  
  1270.  
  1271.  
  1272. (*---------------------------------------------------------------------------
  1273.   Date2Str: Convert a datestamp to a string
  1274.   ---------------------------------------------------------------------------*)
  1275. PROCEDURE Date2Str(date: DosD.Date; VAR strdate,strtime: ARRAY OF CHAR);
  1276.   VAR
  1277.     dt: DosD.DateTime;
  1278.   BEGIN
  1279.     dt.date:=date;
  1280.     dt.format:=DosD.formatDOS;
  1281.     dt.flags:=DosD.DateTimeFlagSet{};
  1282.     dt.strDay:=NIL;
  1283.     dt.strDate:=SYSTEM.ADR(strdate);
  1284.     dt.strTime:=SYSTEM.ADR(strtime);
  1285.     IF DosL.DateToStr(SYSTEM.ADR(dt))=0 THEN
  1286.       String.Copy(strdate,"??-???-??");
  1287.       String.Copy(strtime,"??:??:??");
  1288.     END;
  1289.  
  1290.     WHILE (String.Length(strdate)>0) AND (strdate[String.Length(strdate)-1]=" ") DO
  1291.       (* locale may pad localized strings with spaces under certain conditions. *)
  1292.       (* let's get rid of those unneeded blanks.                                *)
  1293.       strdate[String.Length(strdate)-1]:="\o";
  1294.     END;
  1295.  
  1296.     WHILE (String.Length(strtime)>0) AND (strtime[String.Length(strtime)-1]=" ") DO
  1297.       (* locale may pad localized strings with spaces under certain conditions. *)
  1298.       (* let's get rid of those unneeded blanks.                                *)
  1299.       strtime[String.Length(strtime)-1]:="\o";
  1300.     END;
  1301.   END Date2Str;
  1302.  
  1303.  
  1304. (*---------------------------------------------------------------------------
  1305.   PutChar: put a char into the output buffer
  1306.   ---------------------------------------------------------------------------*)
  1307. PROCEDURE PutChar(chr: CHAR; VAR inx: LONGINT; VAR buf: FormBuffer);
  1308.   BEGIN
  1309.     buf[inx]:=chr;
  1310.     INC(inx);
  1311.     IF inx>=formlen-1 THEN
  1312.       MyError("","output buffer overflow",
  1313.               "your output format is too complex",TRUE);
  1314.     END;
  1315.   END PutChar;
  1316.  
  1317.  
  1318.  
  1319. (*---------------------------------------------------------------------------
  1320.   PutStr: put a string into the output buffer
  1321.   ---------------------------------------------------------------------------*)
  1322. PROCEDURE PutStr(str: ARRAY OF CHAR; wid: LONGINT; VAR inx: LONGINT; VAR buf: FormBuffer);
  1323.   VAR
  1324.     i: LONGINT;
  1325.   BEGIN
  1326.     i:=0;
  1327.     REPEAT
  1328.       PutChar(str[i],inx,buf);
  1329.       INC(i);
  1330.       IF inx>=formlen-1 THEN
  1331.         MyError("","output buffer overflow",
  1332.                 "your output format is too complex",TRUE);
  1333.       END;
  1334.     UNTIL i=String.Length(str);
  1335.  
  1336.     WHILE i<wid DO
  1337.       INC(i);
  1338.       PutChar(" ",inx,buf);
  1339.     END;
  1340.   END PutStr;
  1341.  
  1342.  
  1343.  
  1344. (*---------------------------------------------------------------------------
  1345.   PutInt: convert int->str, put it into the output buffer
  1346.   ---------------------------------------------------------------------------*)
  1347. PROCEDURE PutInt(val: LONGINT; wid: LONGINT; VAR inx: LONGINT; VAR buf: FormBuffer);
  1348.   VAR
  1349.     tmp: ARRAY[0..100] OF CHAR;
  1350.     err: BOOLEAN;
  1351.   BEGIN
  1352.     Conversions.ValToStr(val,TRUE,tmp,10,wid," ",err);
  1353.     PutStr(tmp,wid,inx,buf);
  1354.   END PutInt;
  1355.  
  1356.  
  1357.  
  1358. (*---------------------------------------------------------------------------
  1359.   PutReal: convert real->str, put it into the output buffer
  1360.   ---------------------------------------------------------------------------*)
  1361. PROCEDURE PutReal(val: REAL; wid,decimals: LONGINT; VAR inx: LONGINT; VAR buf: FormBuffer);
  1362.   VAR
  1363.     tmp: ARRAY[0..100] OF CHAR;
  1364.     err: BOOLEAN;
  1365.   BEGIN
  1366.     IF wid<decimals+2 THEN
  1367.       wid:=decimals+2;
  1368.     END;
  1369.     RealConversions.RealToStr(val,tmp,wid,decimals,FALSE,err);
  1370.     PutStr(tmp,wid,inx,buf);
  1371.   END PutReal;
  1372.  
  1373.  
  1374.  
  1375. (*---------------------------------------------------------------------------
  1376.   NextChar: get the next char from the buffer string, increment the index
  1377.             return ASCII.nul when end of buffer is reached
  1378.   ---------------------------------------------------------------------------*)
  1379. (*$ CopyDyn:=FALSE *)
  1380. PROCEDURE NextChar(buf: FormBuffer; VAR inx: LONGINT): CHAR;
  1381.   VAR
  1382.     result: CHAR;
  1383.   BEGIN
  1384.     result:=buf[inx];
  1385.     IF result#"\o" THEN
  1386.       INC(inx);
  1387.     END;
  1388.     RETURN result;
  1389.   END NextChar;
  1390.  
  1391.  
  1392.  
  1393. (*---------------------------------------------------------------------------
  1394.   FormatStats: Format the output and display it. Replaces the old
  1395.                DisplayStats procedure.
  1396.   ---------------------------------------------------------------------------*)
  1397. (*$ CopyDyn:=FALSE *)
  1398. PROCEDURE FormatStats(tot: Statistics; form: FormBuffer);
  1399.   VAR
  1400.     out: FormBuffer;
  1401.     c,c1: CHAR;
  1402.     inx,srcinx: LONGINT;
  1403.     width: LONGINT;
  1404.     date,time: StringT;
  1405.   BEGIN
  1406.     srcinx:=0;
  1407.     inx:=0;
  1408.     c:=NextChar(form,srcinx);
  1409.     REPEAT
  1410.       width:=0;
  1411.       IF c="&" THEN
  1412.         width:=0;
  1413.         c:=NextChar(form,srcinx);
  1414.         IF c="&" THEN
  1415.           PutChar(c,inx,out);
  1416.           c:=NextChar(form,srcinx);
  1417.         ELSE
  1418.           WHILE (c>="0") AND (c<="9") DO
  1419.             width:=width*10+INTEGER(c)-INTEGER("0");
  1420.             IF width>=100 THEN
  1421.               MyError("in STATFORM output format","illegal field width specified",
  1422.                       "the field width must not exceed 99 characters",TRUE);
  1423.             END;
  1424.             c:=NextChar(form,srcinx);
  1425.           END;
  1426.           c1:=NextChar(form,srcinx);
  1427.  
  1428.           IF    (c="F") AND (c1="F") THEN (* host name *)
  1429.             PutChar("\f",inx,out);
  1430.  
  1431.           ELSIF (c="L") AND (c1="F") THEN (* host name *)
  1432.             PutChar("\n",inx,out);
  1433.  
  1434.           ELSIF (c="H") AND (c1="N") THEN (* host name *)
  1435.             IF tot.name[0]="\o" THEN
  1436.               PutStr("TOTAL",width,inx,out);
  1437.             ELSE
  1438.               PutStr(tot.name,width,inx,out);
  1439.             END;
  1440.  
  1441.           ELSIF (c="C") AND (c1="D") THEN (* call direction (long) *)
  1442.             IF tot.in THEN
  1443.               PutStr("incoming",width,inx,out);
  1444.             ELSE
  1445.               PutStr("outgoing",width,inx,out);
  1446.             END;
  1447.  
  1448.           ELSIF (c="c") AND (c1="d") THEN (* call direction (short) *)
  1449.             IF tot.in THEN
  1450.               PutChar("<",inx,out);
  1451.             ELSE
  1452.               PutChar(">",inx,out);
  1453.             END;
  1454.  
  1455.           ELSIF (c="F") AND (c1="D") THEN (* from date *)
  1456.             Date2Str(args.from,date,time);
  1457.             PutStr(date,width,inx,out);
  1458.             PutChar(" ",inx,out);
  1459.             PutStr(time,width,inx,out);
  1460.  
  1461.           ELSIF (c="T") AND (c1="D") THEN (* to date *)
  1462.             Date2Str(args.to,date,time);
  1463.             PutStr(date,width,inx,out);
  1464.             PutChar(" ",inx,out);
  1465.             PutStr(time,width,inx,out);
  1466.  
  1467.           ELSIF (c="A") AND (c1="C") THEN (* number of connects (total) *)
  1468.             PutInt(tot.connects,width,inx,out);
  1469.  
  1470.           ELSIF (c="I") AND (c1="C") THEN (* number of connects (ignored) *)
  1471.             PutInt(tot.ignoreconnects,width,inx,out);
  1472.  
  1473.           ELSIF (c="L") AND (c1="C") THEN (* number of connects (local) *)
  1474.             PutInt(tot.localconnects,width,inx,out);
  1475.  
  1476.           ELSIF (c="O") AND (c1="T") THEN (* online time (total) *)
  1477.             PutInt(tot.seconds,width,inx,out);
  1478.  
  1479.           ELSIF (c="o") AND (c1="t") THEN (* online time (average) *)
  1480.             IF tot.connects=0 THEN
  1481.               PutInt(0,width,inx,out);
  1482.             ELSE
  1483.               PutInt((tot.seconds+tot.connects-1) DIV tot.connects,
  1484.                      width,inx,out);
  1485.             END;
  1486.  
  1487.           ELSIF (c="U") AND (c1="N") THEN (* units (total) *)
  1488.             PutInt(tot.units,width,inx,out);
  1489.  
  1490.           ELSIF (c="u") AND (c1="n") THEN (* units (average *)
  1491.             IF tot.connects=0 THEN
  1492.               PutReal(0.0,width,3,inx,out);
  1493.             ELSE
  1494.               PutReal(REAL(tot.units)/REAL(tot.connects),
  1495.                       width,3,inx,out);
  1496.             END;
  1497.  
  1498.           ELSIF (c="C") AND (c1="O") THEN (* cost (total) *)
  1499.             PutReal(tot.cost,width,3,inx,out);
  1500.  
  1501.           ELSIF (c="c") AND (c1="o") THEN (* cost (average *)
  1502.             IF tot.connects=0 THEN
  1503.               PutReal(0.0,width,3,inx,out);
  1504.             ELSE
  1505.               PutReal(tot.cost/REAL(tot.connects),
  1506.                       width,3,inx,out);
  1507.             END;
  1508.  
  1509.           ELSIF (c="G") AND (c1="R") THEN (* gross read (total) *)
  1510.             PutInt(tot.inbb,width,inx,out);
  1511.  
  1512.           ELSIF (c="g") AND (c1="r") THEN (* gross read (average) *)
  1513.             IF tot.connects=0 THEN
  1514.               PutInt(0,width,inx,out);
  1515.             ELSE
  1516.               PutInt((tot.inbb+tot.connects-1) DIV tot.connects,
  1517.                      width,inx,out);
  1518.             END;
  1519.  
  1520.           ELSIF (c="G") AND (c1="S") THEN (* gross send (total) *)
  1521.             PutInt(tot.outbb,width,inx,out);
  1522.  
  1523.           ELSIF (c="g") AND (c1="s") THEN (* gross send (average) *)
  1524.             IF tot.connects=0 THEN
  1525.               PutInt(0,width,inx,out);
  1526.             ELSE
  1527.               PutInt((tot.outbb+tot.connects-1) DIV tot.connects,
  1528.                      width,inx,out);
  1529.             END;
  1530.  
  1531.           ELSIF (c="N") AND (c1="R") THEN (* net read (total) *)
  1532.             PutInt(tot.inbn,width,inx,out);
  1533.  
  1534.           ELSIF (c="n") AND (c1="r") THEN (* net read (average) *)
  1535.             IF tot.connects=0 THEN
  1536.               PutInt(0,width,inx,out);
  1537.             ELSE
  1538.               PutInt((tot.inbn+tot.connects-1) DIV tot.connects,
  1539.                      width,inx,out);
  1540.             END;
  1541.  
  1542.           ELSIF (c="N") AND (c1="S") THEN (* net send (total) *)
  1543.             PutInt(tot.outbn,width,inx,out);
  1544.  
  1545.           ELSIF (c="n") AND (c1="s") THEN (* net send (average) *)
  1546.             IF tot.connects=0 THEN
  1547.               PutInt(0,width,inx,out);
  1548.             ELSE
  1549.               PutInt((tot.outbn+tot.connects-1) DIV tot.connects,
  1550.                      width,inx,out);
  1551.             END;
  1552.  
  1553.           ELSIF (c="G") AND (c1="C") THEN (* gross cps (average) *)
  1554.             IF tot.seconds=0 THEN
  1555.               PutInt(0,width,inx,out);
  1556.             ELSE
  1557.               PutInt((tot.inbb+tot.outbb) DIV tot.seconds,
  1558.                      width,inx,out);
  1559.             END;
  1560.  
  1561.           ELSIF (c="g") AND (c1="p") THEN (* gross cps (peak) *)
  1562.             PutInt(tot.bpeak,width,inx,out);
  1563.  
  1564.           ELSIF (c="N") AND (c1="C") THEN (* net cps (average) *)
  1565.             IF tot.seconds=0 THEN
  1566.               PutInt(0,width,inx,out);
  1567.             ELSE
  1568.               PutInt((tot.inbn+tot.outbn) DIV tot.seconds,
  1569.                      width,inx,out);
  1570.             END;
  1571.  
  1572.           ELSIF (c="n") AND (c1="p") THEN (* net cps (peak) *)
  1573.             PutInt(tot.npeak,width,inx,out);
  1574.  
  1575.           ELSIF (c="C") AND (c1="G") THEN (* gross cost (average) *)
  1576.             IF tot.inbb+tot.outbb=0 THEN
  1577.               PutReal(0.0,width,3,inx,out);
  1578.             ELSE
  1579.               PutReal(tot.cost/REAL(tot.inbb+tot.outbb)*1048576.0,width,3,inx,out);
  1580.             END;
  1581.  
  1582.           ELSIF (c="C") AND (c1="N") THEN (* net cost (average) *)
  1583.             IF tot.inbn+tot.outbn=0 THEN
  1584.               PutReal(0.0,width,3,inx,out);
  1585.             ELSE
  1586.               PutReal(tot.cost/REAL(tot.inbn+tot.outbn)*1048576.0,width,3,inx,out);
  1587.             END;
  1588.  
  1589.           ELSIF (c="C") AND (c1="S") THEN (* currency symbol *)
  1590.             PutStr(currency,width,inx,out);
  1591.  
  1592.           ELSE
  1593.             out:="field type:   ";
  1594.             out[12]:=c;
  1595.             out[13]:=c1;
  1596.             MyError("in STATFORM output format","illegal field type specified",out,TRUE);
  1597.           END;
  1598.           c:=NextChar(form,srcinx);
  1599.         END;
  1600.       ELSE
  1601.         PutChar(c,inx,out);
  1602.         c:=NextChar(form,srcinx);
  1603.       END;
  1604.     UNTIL srcinx=String.Length(form);
  1605.     InOut.WriteString(out);
  1606.   END FormatStats;
  1607.  
  1608.  
  1609.  
  1610. (*---------------------------------------------------------------------------
  1611.   FormatVerbs: Format the output for DisplayVerb
  1612.   ---------------------------------------------------------------------------*)
  1613. PROCEDURE FormatVerbs(log: Connect; form: FormBuffer);
  1614.   VAR
  1615.     out: FormBuffer;
  1616.     c,c1: CHAR;
  1617.     inx,srcinx: LONGINT;
  1618.     width: LONGINT;
  1619.     date,time: StringT;
  1620.   BEGIN
  1621.     inx:=0;
  1622.     c:=NextChar(form,srcinx);
  1623.     REPEAT
  1624.       width:=0;
  1625.       IF c="&" THEN
  1626.         width:=0;
  1627.         c:=NextChar(form,srcinx);
  1628.         IF c="&" THEN
  1629.           PutChar(c,inx,out);
  1630.           c:=NextChar(form,srcinx);
  1631.         ELSE
  1632.           WHILE (c>="0") AND (c<="9") DO
  1633.             width:=width*10+INTEGER(c)-INTEGER("0");
  1634.             IF width>=100 THEN
  1635.               MyError("in VERBFORM output format","illegal field width specified",
  1636.                       "the field width must not exceed 99 characters",TRUE);
  1637.             END;
  1638.             c:=NextChar(form,srcinx);
  1639.           END;
  1640.           c1:=NextChar(form,srcinx);
  1641.  
  1642.           IF    (c="F") AND (c1="F") THEN (* host name *)
  1643.             PutChar("\f",inx,out);
  1644.  
  1645.           ELSIF (c="L") AND (c1="F") THEN (* host name *)
  1646.             PutChar("\n",inx,out);
  1647.  
  1648.           ELSIF (c="H") AND (c1="N") THEN (* host name *)
  1649.             PutStr(log.host,width,inx,out);
  1650.  
  1651.           ELSIF (c="C") AND (c1="D") THEN (* call direction (long) *)
  1652.             IF NOT log.callout THEN
  1653.               PutStr("incoming",width,inx,out);
  1654.             ELSE
  1655.               PutStr("outgoing",width,inx,out);
  1656.             END;
  1657.  
  1658.           ELSIF (c="c") AND (c1="d") THEN (* call direction (short) *)
  1659.             IF NOT log.callout THEN
  1660.               PutChar("<",inx,out);
  1661.             ELSE
  1662.               PutChar(">",inx,out);
  1663.             END;
  1664.  
  1665.           ELSIF (c="F") AND (c1="D") THEN (* from date *)
  1666.             Date2Str(log.sdate,date,time);
  1667.             PutStr(date,width,inx,out);
  1668.             PutChar(" ",inx,out);
  1669.             PutStr(time,width,inx,out);
  1670.  
  1671.           ELSIF (c="I") AND (c1="C") THEN (* number of connects (ignored) *)
  1672.             IF log.ignore THEN
  1673.               PutChar("Y",inx,out);
  1674.             ELSE
  1675.               PutChar("N",inx,out);
  1676.             END;
  1677.  
  1678.           ELSIF (c="L") AND (c1="C") THEN (* number of connects (local) *)
  1679.             IF log.local THEN
  1680.               PutChar("Y",inx,out);
  1681.             ELSE
  1682.               PutChar("N",inx,out);
  1683.             END;
  1684.  
  1685.           ELSIF (c="O") AND (c1="T") THEN (* online time (total) *)
  1686.             PutInt(log.seconds,width,inx,out);
  1687.  
  1688.           ELSIF (c="U") AND (c1="N") THEN (* units (total) *)
  1689.             PutInt(log.units,width,inx,out);
  1690.  
  1691.           ELSIF (c="C") AND (c1="O") THEN (* cost (total) *)
  1692.             PutReal(log.cost,width,3,inx,out);
  1693.  
  1694.           ELSIF (c="G") AND (c1="R") THEN (* gross read (total) *)
  1695.             PutInt(log.inbb,width,inx,out);
  1696.  
  1697.           ELSIF (c="G") AND (c1="S") THEN (* gross send (total) *)
  1698.             PutInt(log.outbb,width,inx,out);
  1699.  
  1700.           ELSIF (c="N") AND (c1="R") THEN (* net read (total) *)
  1701.             PutInt(log.inbn,width,inx,out);
  1702.  
  1703.           ELSIF (c="N") AND (c1="S") THEN (* net send (total) *)
  1704.             PutInt(log.outbn,width,inx,out);
  1705.  
  1706.           ELSIF (c="G") AND (c1="C") THEN (* gross cps (average) *)
  1707.             IF log.seconds=0 THEN
  1708.               PutInt(0,width,inx,out);
  1709.             ELSE
  1710.               PutInt((log.inbb+log.outbb) DIV log.seconds,
  1711.                      width,inx,out);
  1712.             END;
  1713.  
  1714.           ELSIF (c="N") AND (c1="C") THEN (* net cps (average) *)
  1715.             IF log.seconds=0 THEN
  1716.               PutInt(0,width,inx,out);
  1717.             ELSE
  1718.               PutInt((log.inbn+log.outbn) DIV log.seconds,
  1719.                      width,inx,out);
  1720.             END;
  1721.  
  1722.           ELSIF (c="C") AND (c1="S") THEN (* currency symbol *)
  1723.             PutStr(currency,width,inx,out);
  1724.  
  1725.           ELSE
  1726.             out:="field type:   ";
  1727.             out[12]:=c;
  1728.             out[13]:=c1;
  1729.             MyError("in VERBFORM output format","illegal field type specified",out,TRUE);
  1730.           END;
  1731.           c:=NextChar(form,srcinx);
  1732.         END;
  1733.       ELSE
  1734.         PutChar(c,inx,out);
  1735.         c:=NextChar(form,srcinx);
  1736.       END;
  1737.     UNTIL srcinx=String.Length(form);
  1738.     InOut.WriteString(out);
  1739.   END FormatVerbs;
  1740.  
  1741.  
  1742.  
  1743. (*---------------------------------------------------------------------------
  1744.   UpdateStat: Update the totali/totalo records
  1745.   ---------------------------------------------------------------------------*)
  1746. PROCEDURE UpdateStat(VAR tot: Statistics; VAR log: Connect);
  1747.   VAR
  1748.     tempspeed: LONGINT;
  1749.   BEGIN
  1750.     IF NOT log.ignore THEN
  1751.       INC(tot.connects);
  1752.       tot.seconds:=tot.seconds+log.seconds;
  1753.       tot.inbb:=tot.inbb+log.inbb;
  1754.       tot.outbb:=tot.outbb+log.outbb;
  1755.       tot.inbn:=tot.inbn+log.inbn;
  1756.       tot.outbn:=tot.outbn+log.outbn;
  1757.       tot.units:=tot.units+log.units;
  1758.       tot.cost:=tot.cost+log.cost;
  1759.  
  1760.       IF log.seconds#0 THEN
  1761.         tempspeed:=(log.inbb+log.outbb) DIV log.seconds;
  1762.         IF tempspeed>tot.bpeak THEN
  1763.           tot.bpeak:=tempspeed
  1764.         END;
  1765.  
  1766.         tempspeed:=(log.inbn+log.outbn) DIV log.seconds;
  1767.         IF tempspeed>tot.npeak THEN
  1768.           tot.npeak:=tempspeed
  1769.         END;
  1770.       END;
  1771.     END;
  1772.     IF log.ignore THEN
  1773.       INC(tot.ignoreconnects);
  1774.     END;
  1775.     IF log.local THEN
  1776.       INC(tot.localconnects);
  1777.     END;
  1778.   END UpdateStat;
  1779.  
  1780.  
  1781.  
  1782. (*---------------------------------------------------------------------------
  1783.   ClearStat: Preset a Statistics-record with zeros
  1784.   ---------------------------------------------------------------------------*)
  1785. PROCEDURE ClearStat(VAR stat: Statistics);
  1786.   BEGIN
  1787.     stat.next:=NIL;
  1788.     stat.name:="\o\o";
  1789.     stat.in:=FALSE;
  1790.     stat.connects:=0;
  1791.     stat.inbb:=0;
  1792.     stat.outbb:=0;
  1793.     stat.inbn:=0;
  1794.     stat.outbn:=0;
  1795.     stat.cost:=0.0;
  1796.     stat.units:=0;
  1797.     stat.seconds:=0;
  1798.     stat.bpeak:=0;
  1799.     stat.npeak:=0;
  1800.     stat.localconnects:=0;
  1801.     stat.ignoreconnects:=0;
  1802.   END ClearStat;
  1803.  
  1804.  
  1805.  
  1806. (*---------------------------------------------------------------------------
  1807.   InsertStat: Insert a Statistics-record at the proper position
  1808.               into the linked list
  1809.   ---------------------------------------------------------------------------*)
  1810. PROCEDURE InsertStat(stat: StatisticsPtr);
  1811.   VAR
  1812.     cur,prev: StatisticsPtr;
  1813.     done: BOOLEAN;
  1814.   BEGIN
  1815.     done:=FALSE;
  1816.     IF statroot=NIL THEN
  1817.       statroot:=stat;
  1818.     ELSE
  1819.       cur:=statroot;
  1820.       prev:=NIL;
  1821.       REPEAT
  1822.         IF String.Compare(stat^.name,cur^.name)=0 THEN
  1823.           IF stat^.in THEN
  1824.             (* insert before current *)
  1825.             IF prev=NIL THEN
  1826.               stat^.next:=statroot;
  1827.               statroot:=stat;
  1828.               done:=TRUE;
  1829.             ELSE
  1830.               stat^.next:=prev^.next;
  1831.               prev^.next:=stat;
  1832.               done:=TRUE;
  1833.             END;
  1834.           ELSE
  1835.             (* insert after current *)
  1836.             stat^.next:=cur^.next;
  1837.             cur^.next:=stat;
  1838.             done:=TRUE;
  1839.           END;
  1840.         ELSIF String.Compare(stat^.name,cur^.name)<0 THEN
  1841.           (* insert before current *)
  1842.           IF prev=NIL THEN
  1843.             stat^.next:=statroot;
  1844.             statroot:=stat;
  1845.             done:=TRUE;
  1846.           ELSE
  1847.             stat^.next:=prev^.next;
  1848.             prev^.next:=stat;
  1849.             done:=TRUE;
  1850.           END;
  1851.         ELSE
  1852.           prev:=cur;
  1853.           cur:=cur^.next;
  1854.         END;
  1855.         Break.TestBreak();
  1856.       UNTIL (cur=NIL) OR done;
  1857.       IF cur=NIL THEN
  1858.         prev^.next:=stat;
  1859.       END;
  1860.     END;
  1861.   END InsertStat;
  1862.  
  1863.  
  1864.  
  1865. (*---------------------------------------------------------------------------
  1866.   FindStat: Search linked list for a specific Statistics-record
  1867.             Create that record if not found
  1868.   ---------------------------------------------------------------------------*)
  1869. PROCEDURE FindStat(name: StringT; in: BOOLEAN): StatisticsPtr;
  1870.   VAR
  1871.     cur,next: StatisticsPtr;
  1872.     found: BOOLEAN;
  1873.   BEGIN
  1874.     next:=statroot;
  1875.     IF next#NIL THEN
  1876.       REPEAT
  1877.         cur:=next;
  1878.         next:=cur^.next;
  1879.         found:=(in=cur^.in) AND (String.Compare(name,cur^.name)=0);
  1880.         Break.TestBreak();
  1881.       UNTIL found OR (next=NIL);
  1882.     ELSE
  1883.       found:=FALSE;
  1884.     END;
  1885.     IF found THEN
  1886.       RETURN cur;
  1887.     ELSE
  1888.       next:=ExecL.AllocMem(SIZE(Statistics),ExecD.MemReqSet{});
  1889.       IF next=NIL THEN
  1890.         MyError("","not enough memory","",TRUE);
  1891.       END;
  1892.       ClearStat(next^);
  1893.       next^.name:=name;
  1894.       next^.in:=in;
  1895.       InsertStat(next);
  1896.       RETURN next;
  1897.     END;
  1898.   END FindStat;
  1899.  
  1900.  
  1901.  
  1902. (*---------------------------------------------------------------------------
  1903.   DisplayStatList: Display the entire list of Statistics-records
  1904.   ---------------------------------------------------------------------------*)
  1905. PROCEDURE DisplayStatList;
  1906.   VAR
  1907.     cur: StatisticsPtr;
  1908.   BEGIN
  1909.     cur:=statroot;
  1910.     WHILE cur#NIL DO
  1911.       IF (cur^.in AND args.in) OR ((NOT cur^.in) AND args.out) THEN
  1912.         FormatStats(cur^,statform);
  1913.       END;
  1914.       cur:=cur^.next;
  1915.     END;
  1916.   END DisplayStatList;
  1917.  
  1918.  
  1919.  
  1920. BEGIN
  1921.   SYSTEM.SETREG(11,SYSTEM.ADR(version));
  1922.   rdargsPtr:=NIL;
  1923.   xsroot:=NIL;
  1924.   statroot:=NIL;
  1925.   inf.fh:=NIL;
  1926.   stdErr:=NIL;
  1927.   statform[0]:="\o";
  1928.   verbform[0]:="\o";
  1929.  
  1930.   IF (Arts.kickVersion<37) OR (DosL.dosVersion<37) THEN
  1931.     MyError("","You need OS 2.04 or later (dos.library V37 or later)",
  1932.             "",TRUE);
  1933.   END;
  1934.  
  1935.   Break.InstallException;
  1936.   PresetArgs();
  1937.  
  1938.   IF NOT Arts.wbStarted THEN
  1939.     IF Arts.dosCmdLen>0 THEN
  1940.       temp:=SYSTEM.CAST(StringTPtr,Arts.dosCmdBuf)^;
  1941.       IF temp[0]="?" THEN
  1942.         InOut.WriteString(extendedhelp);
  1943.         Arts.Exit(5);
  1944.       END;
  1945.     END;
  1946.   END;
  1947.  
  1948.   IF DosL.GetVar(SYSTEM.ADR("XSTATARGS"),SYSTEM.ADR(temp),
  1949.                  strlen-2,DosD.VarFlagSet{})#-1 THEN
  1950.     index:=String.Length(temp);
  1951.     IF temp[index-1]#"\n" THEN
  1952.        temp[index]:="\n"; temp[index+1]:="\o";
  1953.     END;
  1954.     ParseDosArgs(temp);
  1955.   END;
  1956.  
  1957.   IF DosL.GetVar(SYSTEM.ADR("XSTATFORM"),SYSTEM.ADR(statform),
  1958.                  SIZE(statform)-2,DosD.VarFlagSet{})=-1 THEN
  1959.     statform:=defstatform;
  1960.   END;
  1961.  
  1962.   IF DosL.GetVar(SYSTEM.ADR("XSTATVERBFORM"),SYSTEM.ADR(verbform),
  1963.                  SIZE(verbform)-2,DosD.VarFlagSet{})=-1 THEN
  1964.     verbform:=defverbform;
  1965.   END;
  1966.  
  1967.   IF NOT Arts.wbStarted THEN
  1968.     IF Arts.dosCmdLen>0 THEN
  1969.       temp:=SYSTEM.CAST(StringTPtr,Arts.dosCmdBuf)^;
  1970.       ParseDosArgs(temp);
  1971.     END;
  1972.   END;
  1973.  
  1974.   IF NOT args.quiet THEN
  1975.     InOut.WriteString("\n\n"+vers+"\n");
  1976.     InOut.WriteString("⌐ Copyright 1992,1993 by Jⁿrgen Weinelt\n");
  1977. (*$ IF BETA *)
  1978.   END;
  1979.   InOut.WriteString("============================================\n"+
  1980.                     " THIS IS A BETA VERSION! DO NOT DISTRIBUTE! \n"+
  1981.                     "============================================\n\n");
  1982. (*$ ELSE *)
  1983.     InOut.WriteString("XStat is Freeware - read the docs for details.\n\n");
  1984.   END;
  1985. (*$ ENDIF *)
  1986.  
  1987.   IF DosL.CompareDates(SYSTEM.ADR(args.from),SYSTEM.ADR(args.to))<0 THEN
  1988.     MyError("","illegal combination of parameters",
  1989.             "FROMDATE is later than TODATE",TRUE);
  1990.   END;
  1991.   IF NOT (args.in OR args.out) THEN
  1992.     MyError("","illegal combination of parameters",
  1993.             "you can't specify NOINCOM and NOOUTGO simultaneously",TRUE);
  1994.   END;
  1995.  
  1996.   IF args.stderr THEN
  1997.      stdErr:=DosL.Open(SYSTEM.ADR("CONSOLE:"),DosD.newFile);
  1998.      IF stdErr=NIL THEN
  1999.         MyError("ERROR:","can't open CONSOLE: for StdErr output",
  2000.                 "guess you'll have to live without it :-)",FALSE);
  2001.      END;
  2002.   END;
  2003.  
  2004.   ClearStat(totali);
  2005.   totali.in:=TRUE;
  2006.   ClearStat(totalo);
  2007.   totalo.in:=FALSE;
  2008.  
  2009.   inf.fh:=DosL.Open(SYSTEM.ADR(args.xstatdata),DosD.readOnly);
  2010.   IF inf.fh=NIL THEN
  2011.     MyError("Can't open XStat.data!","filename was",args.xstatdata,TRUE);
  2012.   END;
  2013.   GetXStatData();
  2014.   DosL.Close(inf.fh);
  2015.   IF currency[0]=ASCII.nul THEN
  2016.     MyError("WARNING","no currency definition found in XStat.data",
  2017.             "using 'DM' as default",FALSE);
  2018.     currency:=" DM";
  2019.   END;
  2020.  
  2021.   inf.fh:=DosL.Open(SYSTEM.ADR(args.xferstat),DosD.readOnly);
  2022.   IF inf.fh=NIL THEN
  2023.     MyError("Can't open Xferstat!","filename was",args.xferstat,TRUE);
  2024.   END;
  2025.  
  2026.   REPEAT
  2027.     Break.TestBreak();
  2028.     GetLog(ln1,ln2);
  2029.     IF String.Length(ln1)+String.Length(ln2)>0 THEN
  2030.       IF ParseLog(ln1,ln2,log) THEN
  2031.         IF (args.host[0]=ASCII.nul) OR
  2032.            (DosL.MatchPatternNoCase(SYSTEM.ADR(args.host),SYSTEM.ADR(log.host))) THEN
  2033.           IF (DosL.CompareDates(SYSTEM.ADR(args.from),SYSTEM.ADR(log.sdate))>=0) AND
  2034.              (DosL.CompareDates(SYSTEM.ADR(log.sdate),SYSTEM.ADR(args.to))>=0) THEN
  2035.             IF ComputeCost(log) THEN
  2036.               IF args.verbose THEN
  2037.                 FormatVerbs(log,verbform);
  2038.               END;
  2039.  
  2040.               IF args.perhost THEN
  2041.                 IF NOT log.ignore THEN
  2042.                   UpdateStat(FindStat(log.host,NOT log.callout)^,log);
  2043.                 END;
  2044.               END;
  2045.               IF log.callout THEN
  2046.                 UpdateStat(totalo,log);
  2047.               ELSE
  2048.                 UpdateStat(totali,log);
  2049.               END;
  2050.  
  2051.             ELSE
  2052.               IF NOT args.quiet THEN
  2053.                 InOut.WriteString("(ignoring this one)\n\n");
  2054.               END;
  2055.             END;
  2056.           END;
  2057.         END;
  2058.       ELSE
  2059.         IF NOT args.quiet THEN
  2060.           InOut.WriteString("(ignoring this one)\n\n");
  2061.         END;
  2062.       END;
  2063.     END;
  2064.   UNTIL inf.eof;
  2065.  
  2066.   IF args.perhost THEN
  2067.     DisplayStatList;
  2068.   END;
  2069.   IF args.in THEN
  2070.     FormatStats(totali,statform);
  2071.   END;
  2072.   IF args.out THEN
  2073.     FormatStats(totalo,statform);
  2074.   END;
  2075. CLOSE
  2076.   IF rdargsPtr#NIL THEN
  2077.     DosL.FreeArgs(rdargsPtr);
  2078.     DosL.FreeDosObject(DosD.dosRdArgs,rdargsPtr);
  2079.     rdargsPtr:=NIL;
  2080.   END;
  2081.   IF xsroot#NIL THEN
  2082.     FreeXData(xsroot);
  2083.     xsroot:=NIL;
  2084.   END;
  2085.   IF statroot#NIL THEN
  2086.     FreeStat(statroot);
  2087.     statroot:=NIL;
  2088.   END;
  2089.   IF inf.fh#NIL THEN
  2090.     DosL.Close(inf.fh);
  2091.     inf.fh:=NIL;
  2092.   END;
  2093.   IF stdErr#NIL THEN
  2094.     DosL.Close(stdErr);
  2095.     stdErr:=NIL;
  2096.   END;
  2097. END XStat.
  2098.