home *** CD-ROM | disk | FTP | other *** search
/ C!T ROM 2 / ctrom_ii_b.zip / ctrom_ii_b / PROGRAM / PASCAL / DAVEY / DAVEYLIB.PAS
Pascal/Delphi Source File  |  1993-11-15  |  69KB  |  1,996 lines

  1. UK Copyright (c) Peter Davey, November 1993
  2.  
  3. This file contains three concatenated Borland Pascal units in source form.
  4.  
  5. Use a standard text editor to separate them.
  6.  
  7. SYS.PAS  adds some definitions and improvements that I use a great deal.
  8. TIMEPACK.PAS  has lots of goodies for date manipulation.
  9. NOVELL.PAS  has a number of useful routines that access the Netware API.
  10.  
  11. --------------------------------------------------------------------------------
  12.  
  13. { Save the first section as SYS.PAS }
  14.  
  15. Unit Sys;
  16.  
  17. Interface
  18.  
  19. USES Dos;
  20.  
  21. CONST
  22.   ValidFileChars  = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890';
  23.   PathLength      = 65;  { This includes the drive designation. }
  24.   MaxStringLength = 255;
  25.   Escape          = #27;
  26.   MaxByte         = $FF;
  27.   ReadOnly        = $01;
  28.   Hidden          = $02;
  29.   SysFile         = $04;
  30.   VolumeID        = $08;
  31.   Directory       = $10;
  32.   Archive         = $20;
  33.   Bell            = #7;
  34.  
  35.  
  36. TYPE
  37.   PathName      = DirStr;
  38.  
  39.   { Cleans up, IMHO, Borland's way of accessing DOS registers and flags. }
  40.   FlagBits      = (CarryFlag, f1, ParityFlag, f3, AuxCarryFlag, f5, ZeroFlag, SignFlag,
  41.                    TrapFlag, InterruptFlag, DirectionFlag, OverflowFlag, f12, f13, f14, f15);
  42.   registers     = record
  43.                     case INTEGER of
  44.                       1 : (ax,bx,cx,dx,bp,si,di,ds,es : WORD;
  45.                           flags : set of FlagBits);
  46.                       2 : (al,ah,bl,bh,cl,ch,dl,dh : BYTE);
  47.                       3 : (Reg : Dos.registers)
  48.                     end;
  49.  
  50.   GenStr        = String[MaxStringLength];
  51.   DOSFileName   = DirStr;
  52.   ShortFileName = String[12];
  53.   { Use the following to get at the keyboard flags. }
  54.   LockFlagSet   = SET OF (RightShift,
  55.                           LeftShift,
  56.                           ControlShift,
  57.                           AltShift,
  58.                           ScrollLock,
  59.                           NumLock,
  60.                           CapsLock,
  61.                           InsertMode);
  62.   DTArecord     = RECORD
  63.                     Reserved  : ARRAY [0 .. 20] OF BYTE;
  64.                     Attribute : BYTE;
  65.                     Time, Date,
  66.                     LowSize,
  67.                     HighSize  : INTEGER;
  68.                     Name      : ARRAY [1 .. 13] OF CHAR
  69.                   END;
  70.   DTAptr        = ^DTArecord;
  71.   CommandStr    = ComStr;
  72.  
  73. VAR
  74.   Locks         : LockFlagSet ABSOLUTE $0000:$0417;
  75.   CommandTail   : ComStr;
  76.   Successful    : BOOLEAN;
  77.  
  78. PROCEDURE Shell(Tail : STRING);
  79.  
  80. PROCEDURE ChMod(Attributes, FileName : DirStr);
  81.  
  82. (****************************************************************************)
  83.  
  84. Implementation
  85.  
  86. (****************************************************************************)
  87.  
  88. PROCEDURE Shell(Tail : STRING);
  89.  
  90. { e.g. Shell('DIR > TEMP'); }
  91.  
  92.   BEGIN
  93.     IF Tail > ' ' THEN Insert(' /C', Tail, 1);
  94.     Exec(GetEnv('COMSPEC'), Tail)
  95.   END;
  96.  
  97. (****************************************************************************)
  98.  
  99. PROCEDURE ChMod(Attributes, FileName : DirStr);
  100.  
  101. { After the Unix call of the same name.  Sets or clears attribute bits. }
  102.  
  103. VAR Current : WORD;
  104.     Count   : BYTE;
  105.     State   : (On, Off);
  106.     F       : FILE;
  107.  
  108.   PROCEDURE SetFlag(Flag : Word);
  109.  
  110.     BEGIN
  111.       IF State=On
  112.         THEN Current := Current OR Flag
  113.         ELSE Current := Current AND NOT Flag
  114.     END;
  115.  
  116.   BEGIN
  117.     Assign(F, FileName);
  118.     GetFattr(F, Current);
  119.     IF DosError = 0 THEN
  120.       BEGIN
  121.         State := On;
  122.         FOR Count := 1 TO length(Attributes) DO
  123.           CASE UpCase(Attributes[Count]) OF
  124.  
  125.             '+' : State := On;
  126.             '-' : State := Off;
  127.             'R' : SetFlag(ReadOnly);
  128.             'H' : SetFlag(Hidden);
  129.             'S' : SetFlag(SysFile);
  130.             'A' : SetFlag(Archive);
  131.  
  132.             ELSE
  133.               BEGIN
  134.                 write('Illegal argument, "');
  135.                 IF State = On
  136.                   THEN Write('+')
  137.                   ELSE Write('-');
  138.                 WriteLn(Attributes[Count], '", to ChMod')
  139.               END
  140.           END;
  141.         SetFattr(F, Current)
  142.       END
  143.   END;
  144.  
  145. (****************************************************************************)
  146.  
  147. { Initialisation part - reads Command Tail before it is zapped! }
  148.  
  149. VAR  Ptr : BYTE;
  150.  
  151.   BEGIN
  152.     Ptr := $80;
  153.     REPEAT
  154.       CommandTail[Ptr AND $7F] := chr(Mem[PrefixSeg:Ptr]);
  155.       inc(Ptr)
  156.     UNTIL Mem[PrefixSeg:Ptr] = 13
  157.   END.
  158.  
  159. --------------------------------------------------------------------------------
  160.  
  161. { Save the next section file as TIMEPACK.PAS }
  162.  
  163. { This is my date and time library.  The formats are UK biased.  It
  164.   would be easily americanised.  Check out StrToDate - accepts most valid
  165.   date formats. }
  166.  
  167. Unit TimePack;
  168.  
  169. InterFace
  170.  
  171. USES   Dos, Sys;
  172.  
  173. { Sorry to give you yet another standard for storing date/time info . . . }
  174.  
  175. TYPE   Date       = RECORD CASE BOOLEAN OF
  176.                       TRUE  : (Year  : BYTE;
  177.                                Month : 1 .. 12;
  178.                                Day   : 1 .. 31);
  179.                       FALSE : (Compare : ARRAY[1 .. 3] OF CHAR)
  180.                     END;
  181.        TimeRec    = RECORD CASE BOOLEAN OF
  182.                       TRUE  : (Hour   : 0 .. 23;
  183.                                Minute,
  184.                                Second : 0 .. 59);
  185.                       FALSE : (Compare : ARRAY[1 .. 3] OF CHAR)
  186.                     END;
  187.        TimeStr    = STRING[8];
  188.        JulianDate = LongInt;
  189.  
  190. FUNCTION DayNum(Dt : Date) : BYTE; { Convert date to day-of-week number.  }
  191.  
  192. FUNCTION DayOfYear(Dt : Date) : WORD; { Ditto to day of year number. }
  193.  
  194. FUNCTION WeekNo(Dt : Date) : BYTE;  { Returns the business week number. }
  195.  
  196. PROCEDURE WeekToDate(Week : WORD; VAR Dt : Date);  { Returns the Monday of given week. }
  197.  
  198. FUNCTION DayOfWeek(Dt : Date) : String;  { Returns the day name of given date. }
  199.  
  200. FUNCTION JulianDay(Dt : Date) : JulianDate;  { Returns a modified serial date number.
  201.                                                Not Julian date as officially defined. }
  202.  
  203. PROCEDURE JulToDate(Jdate : JulianDate; VAR Dt : Date);  { Converts "Julian" number to date }
  204.  
  205. FUNCTION DateDiff(First, Last : Date) : INTEGER;  { The difference in days between two dates. }
  206.  
  207. PROCEDURE DateAdd(Days : INTEGER; VAR Dt : Date);  { Adds so many days to the date. }
  208.  
  209. FUNCTION CompDate(Dt1, Dt2 : Date) : INTEGER;  { -1, 0 or +1 depending on camparison of two dates. }
  210.  
  211. FUNCTION LongDate(Dt : Date) : String;  { Date to full descriptive string. }
  212.  
  213. FUNCTION D__MMM__YY(Dt : Date) : String;  { e.g.  9 Mar 93 }
  214.  
  215. FUNCTION DD_MM_YYYY(Dt : Date) : String;  { e.g.  09-03-1993 }
  216.  
  217. FUNCTION D_M_Y(Dt : Date) : String;  { e.g.  9-3-93 }
  218.  
  219. FUNCTION DD_MMM_YY(Dt : Date) : String;  { 09-Mar-93 }
  220.  
  221. PROCEDURE GetDate(VAR Dt : Date);  { Gets DOS date into date variable }
  222.  
  223. PROCEDURE SetDate(Dt : Date);  { Sets DOS date from date variable }
  224.  
  225. FUNCTION LongTime : LongInt;  {  Returns DOS time since midnight in seconds }
  226.  
  227. FUNCTION Time : TimeStr;  { Returns DOS time as a string HH:MM:SS }
  228.  
  229. PROCEDURE GetTime(VAR Tm : TimeRec);  { Gets DOS time }
  230.  
  231. FUNCTION StrToTime(InputLine : String) : TimeStr;  { Converts a string to a time variable }
  232.  
  233. PROCEDURE SetTime(Tm : TimeStr);  { Sets DOS time }
  234.  
  235. FUNCTION StrToDayOfWeek(St : String) : INTEGER;  { Converts a day name to a day number from 0 - 6 }
  236.  
  237. FUNCTION StrToMonth(St : String) : INTEGER;  { Converts a month name to a number from 1 - 12 }
  238.  
  239. PROCEDURE StrToDate(InputLine : String; VAR Dt : Date; Default : Date);
  240. { Converts a valid date string (any format) to a date variable }
  241.  
  242. (****************************************************************************)
  243.  
  244. Implementation
  245.  
  246. CONST  Months     : ARRAY [1 .. 13] OF INTEGER = ( { March     }   0,
  247.                                                    { April     }  31,
  248.                                                    { May       }  61,
  249.                                                    { June      }  92,
  250.                                                    { July      } 122,
  251.                                                    { August    } 153,
  252.                                                    { September } 184,
  253.                                                    { October   } 214,
  254.                                                    { November  } 245,
  255.                                                    { December  } 275,
  256.                                                    { January   } 306,
  257.                                                    { February  } 337,
  258.                                                    { Dummy     } 366
  259.                                                  );
  260.        MonthName  : ARRAY [1 .. 12] OF STRING [9]
  261.                   = ('January', 'February', 'March', 'April', 'May', 'June',
  262.                      'July', 'August', 'September', 'October', 'November', 'December');
  263.  
  264.        DayName    : ARRAY [0 .. 6] OF STRING [6]
  265.                   = ('Sun', 'Mon', 'Tues', 'Wednes', 'Thurs', 'Fri', 'Satur');
  266.  
  267.        DOSDefaultDate : Date = (Year : 80; Month : 1; Day : 1);
  268.  
  269. (****************************************************************************)
  270.  
  271. FUNCTION DayNum(Dt : Date) : BYTE;     { Convert date to day-of-week number.  }
  272.                                        { This routine uses an arithmetical    }
  273. VAR  Year  : INTEGER;                  { trick very similar to Zeller's       }
  274.      Month : 1 .. 12;                  { congruence algorithm.                }
  275.  
  276.   BEGIN
  277.     Year := Dt.Year + 1900;            { The Date structure works 1900 - 2155 }
  278.     IF Dt.Month > 2                    { We offset the months so that the     }
  279.       THEN Month := Dt.Month - 2       { year begins with March.              }
  280.       ELSE
  281.         BEGIN
  282.           Month := Dt.Month + 10;      { If we are in January or February     }
  283.           Year := pred(Year)           { then we assume the previous year.    }
  284.         END;
  285.     DayNum := (Year                    { Each year advances one day of week.  }
  286.              + Year DIV 4              { Every fourth year is a leap year.    }
  287.              - Year DIV 100            { Except when it is divisible by 100   }
  288.              + Year DIV 400            { unless it is also divisible by 400.  }
  289.              + Month * 16 MOD 27       { This is my answer to Zeller!!        }
  290.              + Dt.Day                  { And of course then we add the day... }
  291.              ) MOD 7                   { ... finally giving the day number.   }
  292.   END;
  293.  
  294. (****************************************************************************)
  295.  
  296. FUNCTION DayOfYear(Dt : Date) : WORD;
  297.  
  298. VAR Temp : WORD;
  299.     LY   : BYTE;
  300.  
  301.   BEGIN
  302.     WITH Dt DO
  303.       BEGIN
  304.         IF Month < 3 THEN
  305.           BEGIN
  306.             Month := Month + 12;
  307.             Year := Year - 1
  308.           END;
  309.         IF Year MOD 4 = 0
  310.           THEN LY := 1
  311.           ELSE LY := 0;
  312.         Temp := 28 * (Month - 3)
  313.               + 13 * (Month + 1) DIV 5
  314.               + Day + 49 + LY;
  315.         IF Temp > (365 + LY) THEN Temp := Temp - 365 - LY;
  316.         DayOfYear := Temp
  317.       END
  318.   END;
  319.  
  320. (****************************************************************************)
  321.  
  322. FUNCTION WeekNo(Dt : Date) : BYTE;
  323.  
  324. VAR JD : WORD;
  325.     LY,
  326.     DN : BYTE;
  327.  
  328.   BEGIN
  329.     DN := DayNum(Dt);
  330.     WITH Dt DO
  331.       BEGIN
  332.         IF Month < 3 THEN
  333.           BEGIN
  334.             Month := Month + 12;
  335.             Year := Year - 1
  336.           END;
  337.         IF Year MOD 4 = 0
  338.           THEN LY := 1
  339.           ELSE LY := 0;
  340.         JD := 28 * (Month - 3)
  341.             + 13 * (Month + 1) DIV 5
  342.             + Day + 49 + LY - DN + 4;
  343.         IF JD > (365 + LY)
  344.           THEN JD := JD - 365 - LY;
  345.         WeekNo := (JD + 6) DIV 7
  346.       END
  347.   END;
  348.  
  349. (****************************************************************************)
  350.  
  351. PROCEDURE WeekToDate(Week : WORD; VAR Dt : Date);  { Returns the Monday of given week. }
  352.  
  353.   BEGIN
  354.     IF Week > 99
  355.       THEN
  356.         BEGIN
  357.           Dt.Year := Week DIV 100;
  358.           IF Dt.Year < 56 THEN Dt.Year := Dt.Year + 100
  359.         END
  360.       ELSE GetDate(Dt);
  361.     Dt.Day := 3;
  362.     Dt.Month := 1;
  363.     JulToDate(JulianDay(Dt) - DayNum(Dt) - 6 + 7 * (Week MOD 100), Dt)
  364.   END;
  365.  
  366. (****************************************************************************)
  367.  
  368. FUNCTION DayOfWeek(Dt : Date) : String;
  369.  
  370.   BEGIN
  371.     DayOfWeek := DayName[DayNum(Dt)] + 'day'
  372.   END;
  373.  
  374. (****************************************************************************)
  375.  
  376. FUNCTION JulianDay(Dt : Date) : JulianDate;
  377.  
  378.   BEGIN
  379.     WITH Dt DO
  380.       BEGIN
  381.         IF Month > 2                   { We offset the months so that the... }
  382.           THEN
  383.             BEGIN
  384.               Month := Month - 2;      { ...  year begins with March.        }
  385.               Year := Year + 4         { The year is offset by 4.            }
  386.             END
  387.           ELSE
  388.             BEGIN
  389.               Month := Month + 10;     { If we are in January or February    }
  390.               Year := Year + 3         { then we assume the previous year.   }
  391.             END;
  392.         JulianDay := JulianDate(Year) * 365
  393.                    + Year DIV 4        { Every fourth year is a leap year    }
  394.                    + Months[Month]     { This adds the necessary constant    }
  395.                    + Day               { And of course then we add the day,  }
  396.                    - 1401              { and subtract the offset value.      }
  397.       END
  398.   END;
  399.  
  400. (****************************************************************************)
  401.  
  402. PROCEDURE JulToDate(Jdate : JulianDate; VAR Dt : Date);
  403.  
  404. VAR  Year  : BYTE;
  405.      Month : 1 .. 12;
  406.  
  407.   BEGIN
  408.     IF Jdate < 1 THEN Jdate := 1;
  409.     Jdate := Jdate + 1401;
  410.     Year := trunc(Jdate / 365.25);         { Extract the year                }
  411.     Jdate := Jdate - Trunc(Year * 365.25); { Reduce Jdate to day of year     }
  412.     IF Jdate < 1                           { Integer multiples of 365.25 are }
  413.       THEN                                 { always 29th February            }
  414.         BEGIN
  415.           Dt.Day := 29;
  416.           Dt.Month := 2;
  417.           Dt.Year := Year - 4
  418.         END
  419.       ELSE
  420.         BEGIN
  421.           Month := 1;                       { Starting from March...         }
  422.           WHILE Months[succ(Month)] < Jdate { Count through the months until }
  423.             DO Month := succ(Month);        { we find the right one, then    }
  424.           Dt.Day := trunc(Jdate - int(Months[Month]));  { subtract to get day of month.  }
  425.           IF Month > 10                     { Since we are beginning our     }
  426.             THEN                            { year with March, we have to    }
  427.               BEGIN                         { correct the offset now.        }
  428.                 Dt.Year := Year - 3;
  429.                 Dt.Month := Month - 10
  430.               END
  431.             ELSE
  432.               BEGIN
  433.                 Dt.Year  := Year - 4;
  434.                 Dt.Month := Month + 2
  435.               END
  436.         END
  437.   END;
  438.  
  439. (****************************************************************************)
  440.  
  441. FUNCTION DateDiff(First, Last : Date) : INTEGER;
  442.  
  443.   BEGIN
  444.     DateDiff := trunc(JulianDay(Last) - JulianDay(First))
  445.   END;
  446.  
  447. (****************************************************************************)
  448.  
  449. PROCEDURE DateAdd(Days : INTEGER; VAR Dt : Date);
  450.  
  451.   BEGIN
  452.     JulToDate(JulianDay(Dt) + Days , Dt)
  453.   END;
  454.  
  455. (****************************************************************************)
  456.  
  457. FUNCTION CompDate(Dt1, Dt2 : Date) : INTEGER;
  458.  
  459.   BEGIN
  460.     IF Dt1.Compare = Dt2.Compare THEN CompDate := 0
  461.       ELSE IF Dt1.Compare > Dt2.Compare
  462.         THEN CompDate := 1
  463.         Else CompDate := -1
  464.   END;
  465.  
  466. (****************************************************************************)
  467.  
  468. FUNCTION LongDate(Dt : Date) : String;
  469.  
  470. VAR  Day    : STRING [2];
  471.      Suffix : STRING [3];
  472.      Year   : STRING [4];
  473.  
  474. begin
  475.   str(Dt.Day, Day);
  476.   str(Dt.Year + 1900, Year);
  477.   IF (Dt.Day MOD 10) = 1
  478.     THEN Suffix := 'st '
  479.     ELSE IF (Dt.Day MOD 10) = 2
  480.       THEN Suffix := 'nd '
  481.       ELSE IF (Dt.Day MOD 10) = 3
  482.         THEN Suffix := 'rd '
  483.         ELSE Suffix := 'th ';
  484.   IF Dt.Day > 10
  485.     THEN IF Dt.Day < 14
  486.       THEN Suffix := 'th ';
  487.   LongDate := DayOfWeek(Dt) + ', '
  488.             + Day + Suffix
  489.             + MonthName[Dt.Month] + ' '
  490.             + Year
  491. end;
  492.  
  493. (****************************************************************************)
  494.  
  495. FUNCTION D__MMM__YY(Dt : Date) : String;
  496.  
  497. var   Day : string[2];
  498.  
  499. begin
  500.   str(Dt.Day, Day);
  501.   D__MMM__YY := day + ' '
  502.               + copy(MonthName[Dt.Month], 1, 3) + ' '
  503.               + chr(Dt.Year MOD 100 DIV 10 + 48) + chr(Dt.Year MOD 10 + 48)
  504. end;
  505.  
  506. (****************************************************************************)
  507.  
  508. FUNCTION DD_MM_YYYY(Dt : Date) : String;
  509.  
  510. VAR Century : STRING[2];
  511.  
  512. begin
  513.   IF Dt.Year > 100 THEN Century := '20'
  514.                    ELSE Century := '19';
  515.   DD_MM_YYYY := chr(Dt.Day DIV 10 + 48) + chr(Dt.Day MOD 10 + 48) + '-'
  516.               + chr(Dt.Month DIV 10 + 48) + chr(Dt.Month MOD 10 + 48) + '-'
  517.               + Century
  518.               + chr(Dt.Year MOD 100 DIV 10 + 48) + chr(Dt.Year MOD 10 + 48)
  519. end;
  520.  
  521. (****************************************************************************)
  522.  
  523. FUNCTION D_M_Y(Dt : Date) : String;
  524.  
  525. VAR  Year, Month, Day : STRING[3];
  526.  
  527. begin
  528.   Str(Dt.Year MOD 100, Year);
  529.   Str(Dt.Month, Month);
  530.   Str(Dt.Day, Day);
  531.   D_M_Y := Day + '-' + Month + '-' + Year
  532. end;
  533.  
  534. (****************************************************************************)
  535.  
  536. FUNCTION DD_MMM_YY(Dt : Date) : String;
  537.  
  538. begin
  539.   DD_MMM_YY := chr(Dt.Day DIV 10 + 48) + chr(Dt.Day MOD 10 + 48) + '-'
  540.              + copy(MonthName[Dt.Month], 1, 3) + '-'
  541.              + chr(Dt.Year MOD 100 DIV 10 + 48) + chr(Dt.Year MOD 10 + 48)
  542. end;
  543.  
  544. (****************************************************************************)
  545.  
  546. PROCEDURE GetDate(VAR Dt : Date);
  547.  
  548. var
  549.   recpack:       registers;              {record for MsDos call}
  550.  
  551. begin
  552.   recpack.Reg.ah := $2a;
  553.   MsDos(recpack.Reg);                        { call function }
  554.   with recpack.Reg do
  555.   begin
  556.     Dt.Year  := cx - 1900;
  557.     Dt.Month := dh;
  558.     Dt.Day   := dl
  559.   end
  560. end;
  561.  
  562. (****************************************************************************)
  563.  
  564. PROCEDURE SetDate(Dt : Date);
  565.  
  566. var
  567.   recpack:       registers;              {record for MsDos call}
  568.  
  569. begin
  570.   with recpack do
  571.   begin
  572.     AH := $2B;
  573.     CX := Dt.Year + 1900;
  574.     DH := Dt.Month;
  575.     DL := Dt.Day
  576.   end;
  577.   MsDos(recpack.Reg)                      { call function }
  578. end;
  579.  
  580. (*****************************************************************************)
  581.  
  582. function LongTime : LongInt;
  583.  
  584. var recpack : registers;                {assign record}
  585.  
  586. begin
  587.   recpack.ah := $2c;
  588.   intr($21,recpack.Reg);                {call interrupt}
  589.   with recpack do
  590.     LongTime := LongInt(ch * 60 + cl) * 60 + dh
  591. end;
  592.  
  593. (****************************************************************************)
  594.  
  595. FUNCTION Time : TimeStr;
  596.  
  597. var
  598.   recpack:          registers;           {assign record}
  599.  
  600. begin
  601.   recpack.ah := $2c;
  602.   MsDOS(recpack.Reg);                    {call interrupt}
  603.   with recpack do
  604.     begin
  605.       time := chr(ch div 10 + 48) + chr(ch mod 10 + 48) + ':'
  606.             + chr(cl div 10 + 48) + chr(cl mod 10 + 48) + ':'
  607.             + chr(dh div 10 + 48) + chr(dh mod 10 + 48)
  608.     end
  609. end;
  610.  
  611. (****************************************************************************)
  612.  
  613. PROCEDURE GetTime(VAR Tm : TimeRec);
  614.  
  615. var
  616.   recpack:          registers;           {assign record}
  617.  
  618.   BEGIN
  619.   recpack.ah := $2c;
  620.   MsDOS(recpack.Reg);                    {call interrupt}
  621.   with recpack do
  622.     begin
  623.       Tm.Hour := CH;
  624.       Tm.Minute := CL;
  625.       Tm.Second := DH
  626.     end
  627.   END;
  628.  
  629. (****************************************************************************)
  630.  
  631. FUNCTION StrToTime(InputLine : String) : TimeStr;
  632.  
  633. (*   Format -  18:16:22
  634.      Node   - 0112334556  *)
  635.  
  636. VAR  Ptr,
  637.      Digits,
  638.      Node,
  639.      Hour,
  640.      Minute,
  641.      Second   : BYTE;
  642.      Ch       : CHAR;
  643.  
  644.   BEGIN
  645.     Node := 0;                              { Initial node is always zero.                  }
  646.     Ptr := 0;
  647.     Hour := 0;
  648.     Minute := 0;
  649.     Second := 0;
  650.     Digits := 0;
  651.     WHILE Ptr < length(InputLine) DO                                      { For each character input . . .  }
  652.       BEGIN
  653.         Ptr := succ(Ptr);
  654.         Ch := InputLine[Ptr];
  655.         CASE Ch OF
  656.           '0' .. '9' : BEGIN
  657.                          Digits := succ(Digits);                          { Count the number of digits read }
  658.                          IF Digits > 2 THEN Node := succ(Node);           { and if necessary go to next node}
  659.                          CASE Node OF
  660.                    0, 2, 4 : BEGIN                                        { Found start of the next number. }
  661.                                Digits := 0;                               { Reset the digit count.          }
  662.                                Node := succ(Node);
  663.                                Ptr := pred(Ptr)                           { Step back over this character.  }
  664.                              END;
  665.                          1 : Hour := Hour * 10 + ord(Ch) - ord('0');      { Accumulate HOUR value.          }
  666.                          3 : Minute := Minute * 10 + ord(Ch) - ord('0');  { Accumulate MINUTE value.        }
  667.                          5 : Second := Second * 10 + ord(Ch) - ord('0');  { Accumulate SECOND value.        }
  668.                          6 : Ptr := length(InputLine)                     { Set final condition.                    }
  669.                          END { CASE }
  670.                        END
  671.           Else CASE Node OF                                               { We get here when we find a separator.   }
  672.             1, 3 : BEGIN
  673.                      Digits := 0;                                         { Reset the digit count.                  }
  674.                      Node := succ(Node)                                   { If in a number, move to separator node. }
  675.                    END;
  676.                5 : Ptr := length(InputLine)                               { Set final condition.                    }
  677.                END { CASE }
  678.         END { CASE }
  679.       END; { WHILE }
  680.     IF Node > 0
  681.       THEN StrToTime := chr(Hour div 10 + 48) + chr(Hour mod 10 + 48) + ':'
  682.                       + chr(Minute div 10 + 48) + chr(Minute mod 10 + 48) + ':'
  683.                       + chr(Second div 10 + 48) + chr(Second mod 10 + 48)
  684.       ELSE StrToTime := Time
  685.   END;
  686.  
  687. (****************************************************************************)
  688.  
  689. PROCEDURE SetTime(Tm : TimeStr);
  690.  
  691. VAR  recpack : registers;
  692.      OK,
  693.      Hour,
  694.      Minute,
  695.      Second  : INTEGER;
  696.  
  697.   BEGIN
  698.     with recpack do
  699.       begin
  700.         AH := $2D;
  701.         Val(copy(Tm, 1, 2), Hour, OK);
  702.         IF OK = 0 THEN
  703.           BEGIN
  704.             Val(copy(Tm, 4, 2), Minute, OK);
  705.             IF OK = 0 THEN
  706.               BEGIN
  707.                 Val(copy(Tm, 7, 2), Second, OK);
  708.                 IF OK = 0 THEN
  709.                   BEGIN
  710.                     CH := Hour;
  711.                     CL := Minute;
  712.                     DH := Second;
  713.                     DL := 5;
  714.                     MsDOS(recpack.Reg)
  715.                   END
  716.               END
  717.           END
  718.       END
  719.   END;
  720.  
  721. (****************************************************************************)
  722.  
  723. FUNCTION StrToDayOfWeek(St : String) : INTEGER;
  724.  
  725. VAR Ptr : INTEGER;                     { Sunday = 0,  Saturday = 6 }
  726.  
  727.   BEGIN
  728.     StrToDayOfWeek := -1;
  729.     FOR Ptr := 6 DOWNTO 0 DO
  730.       IF Pos(St, DayName[Ptr] + 'day') > 0 THEN StrToDayOfWeek := Ptr
  731.   END;
  732.  
  733. (****************************************************************************)
  734.  
  735. FUNCTION StrToMonth(St : String) : INTEGER;
  736.  
  737. VAR Ptr : INTEGER;
  738.  
  739.   BEGIN
  740.     StrToMonth := 0;
  741.     FOR Ptr := 12 DOWNTO 1 DO
  742.       IF Pos(St, MonthName[Ptr]) > 0 THEN StrToMonth := Ptr
  743.   END;
  744.  
  745. (****************************************************************************)
  746.  
  747. PROCEDURE StrToDate(InputLine : String; VAR Dt : Date; Default : Date);
  748.  
  749. (*         13 10 1987       -  format A
  750.     Node: 01123345555
  751.  
  752.            13 October 1987  -  format B
  753.     Node: 0112666666645555
  754.  
  755.            October 13 1987  -  format C
  756.     Node: 0777777789945555
  757.  
  758.            10 13 1987       -  format A (American) - this is assumed by the
  759.                                program ONLY IF day < 13
  760.  
  761.     The separators may be any number of spaces or non-alphanumeric
  762.     characters, and the month name, if used, may be abbreviated to three
  763.     characters.  The routine will in fact extract a valid date, if
  764.     present, from virtually any sentence.  If 100 > year > 80
  765.     then 20th century is assumed.  If year < 80 then 21st century is assumed. *)
  766.  
  767. VAR  Swap,
  768.      Ptr,
  769.      Node,
  770.      WeekDay,
  771.      Day,
  772.      Month,
  773.      Year  : INTEGER;
  774.      Ch    : CHAR;
  775.      MonthStr : String;
  776.  
  777.   BEGIN
  778.     Dt := Default;
  779.     Node := 0;                              { Initial node is always zero.                                                }
  780.     Ptr := 0;
  781.     WeekDay := -1;
  782.     Day := 0;
  783.     Month := 0;
  784.     Year := -1;
  785.     InputLine[0] := succ(InputLine[0]);     { Add a trailing space to the input string.                                   }
  786.     InputLine[length(InputLine)] := ' ';
  787.     WHILE Ptr < length(InputLine) DO        { For each character input . . .                                              }
  788.       BEGIN
  789.         Ptr := succ(Ptr);
  790.         Ch := InputLine[Ptr];
  791.         CASE Ch OF
  792.           '0' .. '9' : CASE Node OF
  793.                       0, 8 : BEGIN                                      { Found start of Day.                             }
  794.                                Node := succ(Node);
  795.                                Day := ord(Ch) - ord('0')
  796.                              END;
  797.                       1, 9 : Day := Day * 10 + ord(Ch) - ord('0');      { Accumulate DAY value, format A or B. }
  798.                          2 : BEGIN                                      { Found while looking for month - Must be format A }
  799.                                Node := 3;
  800.                                Month := ord(Ch) - ord('0')
  801.                              END;
  802.                          3 : Month := Month * 10 + ord(Ch) - ord('0');  { Accumulate MONTH value.                         }
  803.                          4 : BEGIN                                      { Day and month are complete - starting the year. }
  804.                                Node := 5;
  805.                                Year := ord(Ch) - ord('0')
  806.                              END;
  807.                          5 : Year := Year * 10 + ord(Ch) - ord('0');    { Accumulate Year value.                          }
  808.                          6 : BEGIN
  809.                                Month := StrToMonth(MonthStr);           { Found while scanning a month name in format B.  }
  810.                                Ptr := pred(Ptr);                        { Step back over this digit . . .                 }
  811.                                IF Month > 0 THEN Node := 4              { IF month name is OK, assume year comes next.    }
  812.                                             ELSE Node := 2              { otherwise, assume month coming up, in format A. }
  813.                              END;
  814.                          7 : BEGIN
  815.                                Month := StrToMonth(MonthStr);           { Found while scanning a month name in format C.  }
  816.                                Ptr := pred(Ptr);                        { Step back over this digit.                      }
  817.                                IF Month > 0
  818.                                  THEN Node := 8                         { IF month name is OK, assume day comes next.     }
  819.                                  ELSE
  820.                                    BEGIN
  821.                                      WeekDay := StrToDayOfWeek(MonthStr);
  822.                                      IF WeekDay >= 0
  823.                                        THEN Ptr := length(InputLine)
  824.                                        ELSE Node := 0       { otherwise, assume day coming up, in format A.   }
  825.                                    END
  826.                              END;
  827.                          Else writeln('Internal error in StrToDate');
  828.                        END; { CASE }
  829.           'A' .. 'Z',
  830.           'a' .. 'z' : CASE Node OF
  831.                          0 : BEGIN                                      { First valid character . . .                     }
  832.                                Node := 7;                               { . . . must be start of month name, format C.    }
  833.                                MonthStr := UpCase(Ch)
  834.                              END;
  835.                       1, 2 : BEGIN                                      { Found after day.  Must be format B.             }
  836.                                Node := 6;
  837.                                MonthStr := UpCase(Ch)
  838.                              END;
  839.                       3, 9 : Node := 4;                                 { Start looking for the year value.               }
  840.                          5 : Ptr := length(InputLine);                  { Indicates job finished.                         }
  841.                       6, 7 : BEGIN
  842.                                IF Ch < 'a' THEN Ch := chr(ord(Ch) + 32);  { Convert to lower case . . .                   }
  843.                                inc(MonthStr[0]);
  844.                                MonthStr[length(MonthStr)] := Ch         { Then accumulate the month name.                 }
  845.                              END;
  846.                          Else
  847.                        END; { CASE }
  848.           Else CASE Node OF                                             { We get here when we find a separator.           }
  849.                  1 : Node := 2;                                         { Indicates end of DAY, format A or B.            }
  850.               3, 9 : Node := 4;                                         { Ditto end of MONTH, format A, or day, format C. }
  851.                  5 : Ptr := length(InputLine);                          { Indicates job finished.                         }
  852.                  6 : BEGIN
  853.                        Month := StrToMonth(MonthStr);                   { Found while scanning a month name in format B.  }
  854.                        IF Month > 0 THEN Node := 4                      { IF month name is OK, assume year comes next.    }
  855.                                     ELSE Node := 2                      { otherwise, assume month coming up, in format A. }
  856.                      END;
  857.                  7 : BEGIN
  858.                        Month := StrToMonth(MonthStr);                   { Found while scanning a month name in format C.  }
  859.                        IF Month > 0
  860.                          THEN Node := 8                                 { IF month name is OK, assume day comes next.     }
  861.                          ELSE
  862.                            BEGIN
  863.                              WeekDay := StrToDayOfWeek(MonthStr);       { otherwise, check for a day name.                }
  864.                              Node := 0                                  { then, assume day coming up, in format A.        }
  865.                            END
  866.                      END;
  867.                  Else
  868.                END { CASE }
  869.         END { CASE }
  870.       END; { WHILE }
  871.     IF (Day + Month + Year) >= 0
  872.       THEN
  873.         BEGIN
  874.           IF (Month > 12) AND (Day < 13) THEN                           { Fudge factor for "American" dates.              }
  875.             BEGIN                                                       { Not really very clever, but fun.                }
  876.               Swap := Day;
  877.               Day := Month;
  878.               Month := Swap
  879.             END;
  880.           IF (Day > 0) AND (Day < 32) THEN Dt.Day := Day;
  881.           IF (Month > 0) AND (Month < 13) THEN Dt.Month := Month;
  882.           IF Year > 199 THEN Year := (Year - 100) MOD 200;
  883.           IF Year > -1 THEN Dt.Year := Year;
  884.           JulToDate(JulianDay(Dt), Dt)
  885.         END
  886.       ELSE IF WeekDay >= 0
  887.         THEN JulToDate(JulianDay(Dt) + (WeekDay + 7 - DayNum(Dt)) MOD 7, Dt)
  888.   END;
  889.  
  890. (****************************************************************************)
  891.  
  892. END.
  893.  
  894. --------------------------------------------------------------------------------
  895.  
  896. { Save the next section as NOVELL.PAS }
  897.  
  898. { UK Copyright (c) Peter Davey, November 1993 }
  899.  
  900. Unit Novell;
  901.  
  902. {$X+}
  903.  
  904. Interface
  905.  
  906. Uses Dos;
  907.  
  908. TYPE  LockType   = (Temporary, LongTerm);
  909.       String8    = STRING[8];
  910.       String12   = STRING[12];
  911.       String21   = STRING[21];
  912.       String24   = STRING[24];
  913.       String48   = STRING[48];
  914.       CArray48   = ARRAY [1 .. 48] OF CHAR;
  915.       String55   = STRING[55];
  916.       String100  = STRING[100];
  917.  
  918. FUNCTION NetwareInstalled : BOOLEAN;
  919.  
  920. FUNCTION NetwareVersion : WORD;
  921.  
  922. FUNCTION ServerName(Drive : CHAR) : String48;
  923.  
  924. FUNCTION GetConnectionNumber : BYTE;
  925.  
  926. FUNCTION GetUserConnectionNumbers(UserName : String48) : String100;
  927.  
  928. FUNCTION GetUserObjectHexID(UserName : String48) : String8;
  929.  
  930. FUNCTION WorkStationHexID : String12;
  931.  
  932. PROCEDURE SetBroadcastMode(Mode : BYTE);
  933.                                     { Mode 0 - Normal.  Receive all messages }
  934.                                     {      1 - No user messages              }
  935.                                     {      2 - Server stores system messages }
  936.                                     {      3 - Server stores all messages    }
  937.  
  938. PROCEDURE SendBroadcastMessage(Target : String100; Message : String55);
  939.  
  940. FUNCTION GetBroadcastMessage : String55;
  941.  
  942. PROCEDURE SendMessageToUser(Addressee : String48;  Message : String55);
  943.  
  944. PROCEDURE SendMessageToAll(Message : String55);
  945.  
  946. FUNCTION GetUserName : String48;
  947.  
  948. PROCEDURE Lock(FileName : PathStr; TypeOfLock : LockType);
  949.  
  950. PROCEDURE UnLock(FileName : PathStr);
  951.  
  952. PROCEDURE IncrementUserCount(MeterName : STRING);
  953.  
  954. PROCEDURE DecrementUserCount(MeterName : STRING);
  955.  
  956. PROCEDURE Log(LogComment : STRING);
  957.  
  958. PROCEDURE SetServerDateAndTime;
  959.  
  960. PROCEDURE DownServer(Name : String48);
  961.  
  962. (****************************************************************************)
  963.  
  964. Implementation
  965.  
  966. Uses Sys, TimePack;
  967.  
  968. { Timepack is another Peter Davey unit }
  969.  
  970. CONST HexChars : ARRAY [0 .. 15] OF CHAR = '0123456789ABCDEF';
  971.  
  972. TYPE NameList  = ^NameEntry;
  973.      NameEntry = RECORD
  974.                    Name : String21;
  975.                    Next : NameList
  976.                  END;
  977.      RWord     = RECORD
  978.                    H, L : BYTE
  979.                  END;
  980.      RLong     = RECORD Case BOOLEAN OF
  981.                    TRUE  : (B1, B2, B3, B4 : BYTE);
  982.                    FALSE : (L : LongInt)
  983.                  END;
  984.  
  985. VAR  UserList,
  986.      User      : NameList;
  987.      NetError  : INTEGER;
  988.      LockMode  : BYTE;
  989.      GRegs     : Registers;       { Global set of registers for calls to DOS }
  990.      ServName  : CArray48;
  991.  
  992. (****************************************************************************)
  993.  
  994. FUNCTION NetwareInstalled : BOOLEAN;
  995.  
  996. { Returns TRUE if Netware is installed. }
  997.  
  998.   BEGIN
  999.     NetwareInstalled := LockMode = 1
  1000.   END;
  1001.  
  1002. (****************************************************************************)
  1003.  
  1004. PROCEDURE Netware(Call : BYTE;  Request, Reply : POINTER);
  1005.  
  1006. { The standard call procedure for many of the Novell API calls }
  1007.  
  1008.   BEGIN
  1009.     GRegs.AH := Call;
  1010.     GRegs.DS := Seg(Request^);
  1011.     GRegs.SI := Ofs(Request^);
  1012.     GRegs.ES := Seg(Reply^);
  1013.     GRegs.DI := Ofs(Reply^);
  1014.     MsDOS(GRegs.Reg)
  1015.   END;
  1016.  
  1017. (****************************************************************************)
  1018.  
  1019. FUNCTION FileServerInformation(Offset : BYTE) : WORD;
  1020.  
  1021. { Various different data can be returned by the Get File Server Information call.
  1022.   A side-effect of this function is to set ServName to the server name. }
  1023.  
  1024. TYPE Request = RECORD
  1025.                  Len  : WORD;
  1026.                  Func : BYTE
  1027.                END;
  1028.      Reply   = RECORD CASE BOOLEAN OF
  1029.                  TRUE  : ( Len        : WORD;
  1030.                            ServerName : CArray48;      { Offset -  2 }
  1031.                            Version,                    {          50 }
  1032.                            SubVersion : BYTE;          {          51 }
  1033.                            ConnsSupp,                  {          52 }
  1034.                            ConnsInUse,                 {          54 }
  1035.                            MaxVolumes : Rword;         {          56 }
  1036.                            Revision,                   {          58 }
  1037.                            SFTlevel,                   {          59 }
  1038.                            TTSlevel   : BYTE;          {          60 }
  1039.                            PeakConns  : Rword;         {          61 }
  1040.                            { Various version numbers }
  1041.                            Accounting,                 {          63 }
  1042.                            VAP,                        {          64 }
  1043.                            Queueing,                   {          65 }
  1044.                            PrintServer,                {          66 }
  1045.                            Console,                    {          67 }
  1046.                            Security,                   {          68 }
  1047.                            Bridge     : BYTE;          {          69 }
  1048.                            Reserved   : ARRAY [1 .. 60] OF BYTE );
  1049.                  FALSE : ( Linear : ARRAY [0 .. 69] OF BYTE )
  1050.                END;
  1051.  
  1052. VAR  ReqBuf  : Request;
  1053.      RepBuf  : Reply;
  1054.  
  1055.   BEGIN
  1056.     IF LockMode = 1 THEN
  1057.       BEGIN
  1058.         ReqBuf.Len := 1;
  1059.         ReqBuf.Func := $11;
  1060.         RepBuf.Len := 128;
  1061.         Netware($E3, Addr(ReqBuf), Addr(RepBuf));
  1062.         ServName := RepBuf.ServerName;
  1063.         { Some data are words, and others are bytes.
  1064.           The CASE statment sorts them out. }
  1065.         CASE Offset OF
  1066.           50 .. 56, 61 : FileServerInformation := (RepBuf.Linear[Offset] SHL 8) + RepBuf.Linear[Succ(Offset)];
  1067.           Else FileServerInformation := RepBuf.Linear[Offset]
  1068.         End { Case }
  1069.       END
  1070.   END;
  1071.  
  1072. (****************************************************************************)
  1073.  
  1074. FUNCTION NetwareVersion : WORD;
  1075.  
  1076. { Returns both major and minor parts as the high and low order bytes
  1077.   Use: Hi(NetwareVersion)
  1078.        Lo(Netwareversion) }
  1079.  
  1080.  
  1081.   BEGIN
  1082.     NetwareVersion := FileServerInformation(50)
  1083.   END;
  1084.  
  1085. (****************************************************************************)
  1086.  
  1087. FUNCTION ServerName(Drive : CHAR) : String48;
  1088.  
  1089. { Returns the name of the server mapped to the drive given.  If you are on
  1090.   a network with more than one server, this could be relevant to you.  If
  1091.   the drive passed is a local drive, then the name of the DEFAULT server
  1092.   will be returned.  This is USUALLY the server you first logged in to
  1093.   but not always.  See the Novell API documentation. }
  1094.  
  1095. VAR S : PathStr;
  1096.  
  1097.   BEGIN
  1098.     { Note the current logged drive }
  1099.     GetDir(0, S);
  1100.     {$I-}
  1101.     { If a legal drive letter was passed, change to that drive }
  1102.     IF (Drive <> S[1])
  1103.     AND (UpCase(Drive) IN ['A' .. 'Z'])
  1104.       THEN Chdir(Drive + ':');
  1105.     {$I+}
  1106.     Drive := S[1];
  1107.     { If the change drive produced an error, IOresult will be non-zero
  1108.       and a null server name is returned. }
  1109.     IF IOresult = 0
  1110.       THEN
  1111.         BEGIN
  1112.           FileServerInformation(50);            { Ignore the returned value }
  1113.           S := ServName;
  1114.           WHILE S[Length(S)] <= ' ' DO Delete(S, Length(S), 1);
  1115.           ServerName := S
  1116.         END
  1117.       ELSE ServerName := '';
  1118.     { Change back to the original drive }
  1119.     Chdir(Drive + ':')
  1120.   END;
  1121.  
  1122. (****************************************************************************)
  1123.  
  1124. FUNCTION GetConnectionNumber : BYTE;
  1125.  
  1126. { Returns the connection number of this workstation }
  1127.  
  1128.   BEGIN
  1129.     IF LockMode = 1
  1130.       THEN
  1131.         BEGIN
  1132.           Gregs.AX := $DC00;
  1133.           MsDos(Gregs.Reg);
  1134.           GetConnectionNumber := Gregs.AL
  1135.         END
  1136.       ELSE GetConnectionNumber := 0
  1137.   END;
  1138.  
  1139. (****************************************************************************)
  1140.  
  1141. FUNCTION GetUserConnectionNumbers(UserName : String48) : String100;
  1142.  
  1143. { Returns a character string of BYTES which are the connection numbers of
  1144.   the given named user.  The reason for the string is that a Novell user
  1145.   can be logged on at many stations.  We think 100 is a reasonable max!! }
  1146.  
  1147. TYPE Request = RECORD
  1148.                  Len        : WORD;
  1149.                  Func       : BYTE;
  1150.                  ObjectType : RWord;
  1151.                  ObjectName : String48
  1152.                END;
  1153.      Reply   = RECORD
  1154.                  Len  : WORD;
  1155.                  List : String100
  1156.                END;
  1157.  
  1158. VAR  ReqBuf  : Request;
  1159.      RepBuf  : Reply;
  1160.  
  1161.   BEGIN
  1162.     IF LockMode = 1
  1163.       THEN
  1164.         BEGIN
  1165.           ReqBuf.Len          := 52;
  1166.           ReqBuf.Func         := $15;
  1167.           ReqBuf.ObjectType.H := 0;
  1168.           ReqBuf.ObjectType.L := 1;
  1169.           ReqBuf.ObjectName   := UserName;
  1170.           RepBuf.Len          := 101;
  1171.           RepBuf.List         := '';
  1172.           Netware($E3, Addr(ReqBuf), Addr(RepBuf));
  1173.           GetUserConnectionNumbers := RepBuf.List
  1174.         END
  1175.       ELSE GetUserConnectionNumbers := ''
  1176.   END;
  1177.  
  1178. (****************************************************************************)
  1179.  
  1180. FUNCTION GetUserObjectHexID(UserName : String48) : String8;
  1181.  
  1182. { Every user has a HEX identity.  This is stored in the bindery. }
  1183.  
  1184. TYPE Request = RECORD
  1185.                  Len        : WORD;
  1186.                  Func       : BYTE;
  1187.                  ObjectType : RWord;
  1188.                  ObjectName : String48
  1189.                END;
  1190.      Reply   = RECORD
  1191.                  Len        : WORD;
  1192.                  ObjectID   : RLong;
  1193.                  ObjectType : RWord;
  1194.                  ObjectName : Array [1 .. 48] OF Char;
  1195.                END;
  1196.  
  1197. VAR  ReqBuf  : Request;
  1198.      RepBuf  : Reply;
  1199.  
  1200.   BEGIN
  1201.     IF LockMode = 1
  1202.       THEN
  1203.         BEGIN
  1204.           ReqBuf.Len          := 52;
  1205.           ReqBuf.Func         := $35;
  1206.           ReqBuf.ObjectType.H := 0;
  1207.           ReqBuf.ObjectType.L := 1;
  1208.           ReqBuf.ObjectName   := UserName;
  1209.           RepBuf.Len          := 54;
  1210.           RepBuf.ObjectID.B1  := 0;
  1211.           RepBuf.ObjectID.B2  := 0;
  1212.           RepBuf.ObjectID.B3  := 0;
  1213.           RepBuf.ObjectID.B4  := 0;
  1214.           Netware($E3, Addr(ReqBuf), Addr(RepBuf));
  1215.           GetUserObjectHexID := HexChars[RepBuf.ObjectID.B1 DIV $10]
  1216.                               + HexChars[RepBuf.ObjectID.B1 MOD $10]
  1217.                               + HexChars[RepBuf.ObjectID.B2 DIV $10]
  1218.                               + HexChars[RepBuf.ObjectID.B2 MOD $10]
  1219.                               + HexChars[RepBuf.ObjectID.B3 DIV $10]
  1220.                               + HexChars[RepBuf.ObjectID.B3 MOD $10]
  1221.                               + HexChars[RepBuf.ObjectID.B4 DIV $10]
  1222.                               + HexChars[RepBuf.ObjectID.B4 MOD $10]
  1223.         END
  1224.       ELSE GetUserObjectHexID := '0';
  1225.   END;
  1226.  
  1227. (****************************************************************************)
  1228.  
  1229. FUNCTION WorkStationHexID : String12;
  1230.  
  1231. { Every workstation has a HEX ID.  This call returns the ID of the workstation. }
  1232.  
  1233. TYPE Reply   = ARRAY [0 .. 9] OF BYTE;
  1234.  
  1235. VAR  RepBuf  : ^Reply;
  1236.  
  1237.   BEGIN
  1238.     IF LockMode = 1
  1239.       THEN
  1240.         BEGIN
  1241.           WITH GRegs DO
  1242.             BEGIN
  1243.               BX := 9;               { Don't know why, but this call     }
  1244.               Intr($7A, GRegs.Reg);  { doesn't always succeed first time. }
  1245.               BX := 9;
  1246.               Intr($7A, GRegs.Reg);
  1247.               RepBuf := Ptr(ES, SI)
  1248.             END;
  1249.           WorkStationHexID := HexChars[RepBuf^[4] DIV $10]                            + HexChars[RepBuf^[4] MOD $10]
  1250.                             + HexChars[RepBuf^[5] DIV $10]
  1251.                             + HexChars[RepBuf^[5] MOD $10]
  1252.                             + HexChars[RepBuf^[6] DIV $10]
  1253.                             + HexChars[RepBuf^[6] MOD $10]
  1254.                             + HexChars[RepBuf^[7] DIV $10]
  1255.                             + HexChars[RepBuf^[7] MOD $10]
  1256.                             + HexChars[RepBuf^[8] DIV $10]
  1257.                             + HexChars[RepBuf^[8] MOD $10]
  1258.                             + HexChars[RepBuf^[9] DIV $10]
  1259.                             + HexChars[RepBuf^[9] MOD $10]
  1260.         END
  1261.       ELSE WorkStationHexID := ''
  1262.   END;
  1263.  
  1264. (****************************************************************************)
  1265.  
  1266. PROCEDURE SetBroadcastMode(Mode : BYTE);
  1267.  
  1268. { This is the API call used by CASTOFF and CASTON }
  1269.  
  1270. { Mode 0 - Normal.  Receive all messages }
  1271. {      1 - No user messages              }
  1272. {      2 - Server stores system messages }
  1273. {      3 - Server stores all messages    }
  1274.  
  1275.   BEGIN
  1276.     Gregs.AX := $DEFF;
  1277.     Gregs.DL := Mode;
  1278.     MsDos(Gregs.Reg);
  1279.     Successful := Gregs.AL = Mode
  1280.   END;
  1281.  
  1282. (****************************************************************************)
  1283.  
  1284. PROCEDURE SendBroadcastMessage(Target : String100; Message : String55);
  1285.  
  1286. { Send to a list of station numbers }
  1287.  
  1288. TYPE Request = RECORD
  1289.                  Len     : WORD;
  1290.                  Func    : BYTE;
  1291.                  List    : String100;
  1292.                  Message : String55
  1293.                END;
  1294.      Reply   = RECORD
  1295.                  Len     : WORD;
  1296.                  List    : String100
  1297.                END;
  1298.  
  1299. VAR  ReqBuf  : Request;
  1300.      RepBuf  : Reply;
  1301.      Station : CHAR;
  1302.  
  1303.   BEGIN
  1304.     Successful := FALSE;
  1305.     IF LockMode = 1 THEN
  1306.       Station := Chr(GetConnectionNumber);
  1307.       WHILE Pos(Station, Target) > 0 DO Delete(Target, Pos(Station, Target), 1);
  1308.       IF Target > '' THEN
  1309.         IF Message > '' THEN
  1310.           BEGIN
  1311.             ReqBuf.Len     := 158;
  1312.             ReqBuf.Func    := 0;
  1313.             ReqBuf.List    := Target;
  1314.             ReqBuf.Message := Message;
  1315.             Move(ReqBuf.Message,
  1316.                  ReqBuf.List[Succ(Length(ReqBuf.List))],
  1317.                  Succ(Length(ReqBuf.Message)));
  1318.             RepBuf.Len     := 101;
  1319.             Netware($E1, Addr(ReqBuf), Addr(RepBuf));
  1320.             Successful := GRegs.AL = 0
  1321.           END
  1322.   END;
  1323.  
  1324. (****************************************************************************)
  1325.  
  1326. FUNCTION GetBroadcastMessage : String55;
  1327.  
  1328. { You can retrieve stored messages from the server any time if you have set
  1329.   broadcast mode to 2 or 3 using SetBroadcastMode.  If there is no messge a
  1330.   null string is returned. }
  1331.  
  1332. TYPE Request = RECORD
  1333.                  Len     : WORD;
  1334.                  Func    : BYTE
  1335.                END;
  1336.      Reply   = RECORD
  1337.                  Len     : WORD;
  1338.                  Message : String55
  1339.                END;
  1340.  
  1341. VAR  ReqBuf  : Request;
  1342.      RepBuf  : Reply;
  1343.  
  1344.   BEGIN
  1345.     ReqBuf.Len     := 1;
  1346.     ReqBuf.Func    := 1;
  1347.     RepBuf.Len     := 56;
  1348.     Netware($E1, Addr(ReqBuf), Addr(RepBuf));
  1349.     IF Gregs.AL = 0
  1350.       THEN GetBroadcastMessage := RepBuf.Message
  1351.       ELSE GetBroadcastMessage := ''
  1352.   END;
  1353.  
  1354. (****************************************************************************)
  1355.  
  1356. PROCEDURE SendMessageToUser(Addressee : String48;  Message : String55);
  1357.  
  1358. { Send message to a single user.  The message goes to all stations where
  1359.   the named user is logged on. }
  1360.  
  1361.   BEGIN
  1362.     Successful := FALSE;
  1363.     IF LockMode = 1 THEN
  1364.       BEGIN
  1365.         IF Addressee = 'SUPERVIS' THEN Addressee := 'SUPERVISOR';
  1366.         SendBroadcastMessage(GetUserConnectionNumbers(Addressee), Message)
  1367.       END
  1368.   END;
  1369.  
  1370. (****************************************************************************)
  1371.  
  1372. PROCEDURE SendMessageToAll(Message : String55);
  1373.  
  1374. { Send a message to all logged on.  This is NOT the same as the EVERYONE
  1375.   group, because it is possible to remove people from EVERYONE.  This code
  1376.   assumes 100 user Netware.  For the 250 user version I assume you would
  1377.   need to change the loop, and rewrite SendBroadcastMessage.  My API
  1378.   documentation does not make it clear. }
  1379.  
  1380. VAR FullList : String100;
  1381.  
  1382.   BEGIN
  1383.     Successful := FALSE;
  1384.     IF LockMode = 1 THEN
  1385.       BEGIN
  1386.         { Generate a string of the bytes from 1 to 100 }
  1387.         FOR FullList[0] := #1 TO #100 DO
  1388.           FullList[Length(FullList)] := FullList[0];
  1389.         FullList[0] := #100;
  1390.         SendBroadcastMessage(FullList, Message)
  1391.       END
  1392.   END;
  1393.  
  1394. (****************************************************************************)
  1395.  
  1396. FUNCTION GetUserName : String48;
  1397.  
  1398. { Return the user's logon name.  This will return the value of the
  1399.   environment variable ID if Netware is not running. }
  1400.  
  1401. TYPE  Type1    = RECORD
  1402.                    BufLen : WORD;
  1403.                    Func   : BYTE
  1404.                  END;
  1405.       Type2    = RECORD
  1406.                    BufLen : WORD;
  1407.                    Mask   : BYTE;
  1408.                    ID     : RLong
  1409.                  END;
  1410.       Type3    = RECORD
  1411.                    BufLen : WORD;
  1412.                    ID     : RLong;
  1413.                    ObjTyp : RWord;
  1414.                    Name   : String48
  1415.                  END;
  1416.  
  1417. VAR   CallBuf  : Type1;
  1418.       GenBuf   : Type2;
  1419.       NameBuf  : Type3;
  1420.  
  1421.   BEGIN
  1422.     IF LockMode = 1
  1423.       THEN
  1424.         BEGIN
  1425.           CallBuf.BufLen   := 1;
  1426.           CallBuf.Func     := 70;             { Get bindery access. }
  1427.  
  1428.           GenBuf.BufLen    := 5;
  1429.           GenBuf.Mask      := 0;
  1430.           GenBuf.ID.B4     := 0;
  1431.           GenBuf.ID.B3     := 0;
  1432.           GenBuf.ID.B2     := 0;
  1433.           GenBuf.ID.B1     := 0;
  1434.  
  1435.           Netware($E3, Addr(CallBuf), Addr(GenBuf));
  1436.  
  1437.           IF (GenBuf.ID.B1 OR GenBuf.ID.B2 OR GenBuf.ID.B3 OR GenBuf.ID.B4) <> 0
  1438.             THEN
  1439.               BEGIN
  1440.                 GenBuf.BufLen    := 5;
  1441.                 GenBuf.Mask      := 54;       { Get name from bindery. }
  1442.  
  1443.                 NameBuf.BufLen   := 54;
  1444.  
  1445.                 Netware($E3, Addr(GenBuf), Addr(NameBuf));
  1446.  
  1447.                 IF GRegs.AL <> 0
  1448.                   THEN GetUserName := GetEnv('ID')
  1449.                   ELSE
  1450.                     BEGIN
  1451.                       Move(NameBuf.Name[0], NameBuf.Name[1], 48);
  1452.                       NameBuf.Name[0] := Chr(48);
  1453.                       NameBuf.Name[0] := Chr(Pred(Pos(#0, NameBuf.Name)));
  1454.                       GetUserName := NameBuf.Name
  1455.                     END
  1456.               END
  1457.             ELSE GetUserName := GetEnv('ID')
  1458.         END
  1459.       ELSE GetUserName := GetEnv('ID')
  1460.   END;
  1461.  
  1462. (****************************************************************************)
  1463.  
  1464. FUNCTION Minute(St : STRING) : INTEGER;
  1465.  
  1466.   FUNCTION Digit (Ch : CHAR) : BYTE;
  1467.  
  1468.     BEGIN
  1469.       Digit := Ord(Ch) - 48
  1470.     END;
  1471.  
  1472.   BEGIN
  1473.     IF Length(St) > 4
  1474.       THEN Minute := (Digit(St[1]) * 10 + Digit(St[2])) * 60
  1475.                     + Digit(St[4]) * 10 + Digit(St[5])
  1476.       ELSE Minute := 0
  1477.   END;
  1478.  
  1479. (****************************************************************************)
  1480.  
  1481. { Generates a suitable name for a lock file.  This is only used if
  1482.   Netware locking is not available. }
  1483.  
  1484. FUNCTION LockName(Name : PathStr) : PathStr;
  1485.  
  1486. VAR Ptr : BYTE;
  1487.  
  1488.   BEGIN
  1489.     Ptr := length(Name);
  1490.     WHILE Ptr > 0 DO
  1491.       BEGIN
  1492.         CASE Name[Ptr] OF
  1493.           '\' : Ptr := 1;
  1494.           '.' : BEGIN
  1495.                   Name[0] := chr(pred(Ptr));
  1496.                   Ptr := 1
  1497.                 END;
  1498.           ' ' : Delete(Name, Ptr, 1)
  1499.         END;
  1500.         Ptr := pred(Ptr)
  1501.       END;
  1502.     LockName := Name + '.LOK'
  1503.   END;
  1504.  
  1505. (****************************************************************************)
  1506.  
  1507. PROCEDURE Lock(FileName : PathStr; TypeOfLock : LockType);
  1508.  
  1509. { Attempts to lock a file.  The global BOOLEAN variable Successful contains
  1510.   the result.  If Netware is not loaded, a generic method using semaphore
  1511.   files is attempted.  The semaphores have the extension .LOK
  1512.  
  1513.   The TypeOfLock is ignored if Netware locking is used.  With the generic
  1514.   locking it controls behaviour if a lock is found.  If you pass
  1515.   TypeOfLock := Temporary then the procedure will ignore a lock file and
  1516.   return successfully if the lock is more than five minutes old.  If you
  1517.   pass TypeOfLock := LongTerm then the procedure will always fail if an
  1518.   existing lock, however old, is found. }
  1519.  
  1520. VAR   Count    : INTEGER;
  1521.       Message,
  1522.       LockFile : PathStr;
  1523.       Now      : STRING[8];
  1524.       Handle   : INTEGER;
  1525.  
  1526.   (************************************)
  1527.  
  1528.   PROCEDURE WriteMessage;
  1529.  
  1530.     BEGIN
  1531.       WITH GRegs DO
  1532.         BEGIN
  1533.           AX := $4200;           { Move file pointer ref start    }
  1534.           BX := Handle;          { of file with this handle.      }
  1535.           CX := 0;               { With offset ...                }
  1536.           DX := 0;               {                 at first byte. }
  1537.           MsDos(GRegs.Reg);
  1538.           Message := Time + '  Locked by ' + GetUserName + #0;
  1539.           AH := $40;             { Write to sequential file       }
  1540.           CX := length(message); { Length of string to write.     }
  1541.           DS := seg(Message[1]);
  1542.           DX := ofs(Message[1]);
  1543.           MsDOS(GRegs.Reg);
  1544.           Successful := TRUE
  1545.         END
  1546.     END;
  1547.  
  1548.   (************************************)
  1549.  
  1550.   BEGIN
  1551.     WITH Gregs DO
  1552.       IF LockMode = 1
  1553.         THEN
  1554.           { If Netware is loaded, the Netware locking call is used.  The
  1555.             procedure will set Successful = TRUE if the lock is placed
  1556.             within five seconds. }
  1557.           BEGIN
  1558.             Insert(#0, FileName, Succ(Length(FileName)));
  1559.             AX := $EB01;
  1560.             DS := Seg(FileName[1]);
  1561.             DX := Ofs(FileName[1]);
  1562.             BP := 90;  { Five second wait time }
  1563.             MsDos(Gregs.Reg);
  1564.             Successful := AL = 0
  1565.           END
  1566.         ELSE
  1567.           BEGIN
  1568.             Successful := FALSE;
  1569.             LockFile := LockName(FileName);
  1570.             LockFile[Succ(Length(LockFile))] := #0;
  1571.             Count := 240;                  { Make 240 attempts to lock }
  1572.             DS := seg(LockFile[1]);
  1573.             DX := ofs(LockFile[1]);
  1574.             CX := 0;                       { Open file with normal attributes. }
  1575.             REPEAT
  1576.               Count := pred(Count);
  1577.               AH := $5B;                   { Create new file (semaphore) }
  1578.               MsDOS(GRegs.Reg);            { Call DOS }
  1579.               IF (CarryFlag IN Flags)
  1580.                 THEN
  1581.                   IF AX < 5                { Path does not exist, or no }
  1582.                     THEN Count := -1       { handles are available. }
  1583.                     ELSE { Nothing }
  1584.                 ELSE
  1585.                   BEGIN
  1586.                     Handle := AX;          { Handle of opened file }
  1587.                     WriteMessage;          { Note who locked it }
  1588.                     AH := $3E;
  1589.                     MsDOS(GRegs.Reg);      { Close the file again!! }
  1590.                     Count := -1            { Indicate success }
  1591.                   END
  1592.             UNTIL Count < 1;
  1593.             IF Count = 0 THEN              { Existing lock still in place }
  1594.               BEGIN
  1595.                 AX := $3D12;               { Open file for read/write.  Deny access. }
  1596.                 MsDOS(GRegs.Reg);
  1597.                 IF NOT (CarryFlag IN Flags) THEN
  1598.                   BEGIN
  1599.                     Handle := AX;          { Handle of opened file. }
  1600.                     BX := Handle;
  1601.                     AH := $3F;             { Read file. }
  1602.                     CX := pred(sizeof(message));
  1603.                     DS := seg(Message[1]);
  1604.                     DX := ofs(Message[1]);
  1605.                     MsDOS(GRegs.Reg);      { Find out who owns the lock.  If this
  1606.                                              fails, take the lock anyway. }
  1607.                     IF (NOT (CarryFlag IN Flags)) AND (AX > 18)
  1608.                       THEN
  1609.                         BEGIN
  1610.                           Message[0] := Chr(AX);
  1611.                           Message[0] := Chr(Pos(#0, Message))
  1612.                         END
  1613.                       ELSE Message := '  ' + GetUserName + #0;
  1614.                     IF pos(' ' + GetUserName + #0, Message) > 1
  1615.                       THEN WriteMessage
  1616.                       ELSE
  1617.                         BEGIN
  1618.                           write(Message);  { Display lock owner's ID }
  1619.                           IF (Message[3] = ':') AND (TypeOfLock = Temporary)
  1620.                           { This is a bit of a kludge to make generic locking
  1621.                             work.  If you state in the call that the lock is
  1622.                             a temporary one, the following code will ignore
  1623.                             an existing lock if its time is not within five
  1624.                             minutes of current time.  If someone else has
  1625.                             crashed and left a lock in place this clears it. }
  1626.                             THEN
  1627.                               BEGIN
  1628.                                 IF (Minute(Time) < (Minute(Message) - 5))
  1629.                                 OR (Minute(Time) > (Minute(Message) + 5))
  1630.                                   THEN
  1631.                                     BEGIN
  1632.                                       Write('- Old lock cleared');
  1633.                                       WriteMessage
  1634.                                     END
  1635.                               END
  1636.                         END;
  1637.                       AH := $3E;
  1638.                       MsDOS(GRegs.Reg)       { Close the file again!! }
  1639.                   END
  1640.               END
  1641.           END
  1642.   END;
  1643.  
  1644. (****************************************************************************)
  1645.  
  1646. PROCEDURE UnLock(FileName : PathStr);
  1647.  
  1648. { Unlocks a file, or if Netware is not loaded, deletes the semaphore file }
  1649.  
  1650. VAR LockFile : FILE;
  1651.  
  1652.   BEGIN
  1653.     IF LockMode = 1
  1654.       THEN WITH Gregs DO
  1655.         BEGIN
  1656.           { The Novell file locking method }
  1657.           Insert(#0, FileName, Succ(Length(FileName)));
  1658.           AX := $EDFF;
  1659.           DS := Seg(FileName[1]);
  1660.           DX := Ofs(FileName[1]);
  1661.           MsDos(Gregs.Reg)
  1662.         END
  1663.       ELSE
  1664.         BEGIN
  1665.           { The semaphore file method }
  1666.           Assign(LockFile, LockName(FileName));
  1667.           {$I-} Erase(LockFile); {$I+}
  1668.           IF IOresult = 0 THEN { Ignore the result of the action }
  1669.         END
  1670.   END;
  1671.  
  1672. (****************************************************************************)
  1673.  
  1674. FUNCTION ReadUsers(VAR MeterFile : TEXT;  VAR User : NameList) : INTEGER;
  1675.  
  1676. { Read the users' names from a licence metering file }
  1677.  
  1678. VAR Count  : INTEGER;
  1679.     Check1 : String21;
  1680.     Check2 : String[9];
  1681.     Today  : Date;
  1682.  
  1683.   BEGIN
  1684.     Count := 0;
  1685.     Check1 := ' ' + GetUserName + ' ';
  1686.     GetDate(Today);
  1687.     Check2 := D__MMM__YY(Today);
  1688.     WHILE NOT SeekEOF(MeterFile) DO
  1689.       BEGIN
  1690.         ReadLn(MeterFile, User^.Name);
  1691.         IF (Pos(Check1, User^.Name) < 8)
  1692.         AND (Pos(Check2, User^.Name) > 0) THEN
  1693.           BEGIN
  1694.             Inc(Count);
  1695.             NEW(User^.Next);
  1696.             User^.Next^.Next := NIL;
  1697.             User := User^.Next
  1698.           END
  1699.       END;
  1700.     ReadUsers := Count
  1701.   END;
  1702.  
  1703. (****************************************************************************)
  1704.  
  1705. PROCEDURE IncrementUserCount(MeterName : STRING);
  1706.  
  1707. { Adds the current user's name to a licence metering file.  The global
  1708.   BOOLEAN variable Successful holds the result.  The maximum number of
  1709.   users allowed to be added is in the first line of the meter file.  The
  1710.   routine will never add the same user twice.  Use this call to limit the
  1711.   number of users allowed to acces a function simultaneously.
  1712.  
  1713.   If the procedure fails because the maximum number of users are already
  1714.   listed in the meter file, then a Netware broadcast is sent to all of
  1715.   them to warn that somebody else requires access.
  1716.  
  1717.   If the file does not exist, it is created with a user limit of 1.
  1718.  
  1719.   Create your meter files in the first place by hand, e.g.:
  1720.   ECHO 15 > MYFILE.MTR
  1721.  
  1722.   The system will maintain a "High water mark" as the second line of the
  1723.   file.  This shows the maximum number of users who have been in there
  1724.   concurrently. }
  1725.  
  1726. VAR  MeterFile  : TEXT;
  1727.      Count,
  1728.      HighWater,
  1729.      Limit     : INTEGER;
  1730.      Ptr       : BYTE;
  1731.      Today     : Date;
  1732.      Directory : DirStr;
  1733.      FileName  : NameStr;
  1734.      Ext       : ExtStr;
  1735.  
  1736.   BEGIN
  1737.     Lock(MeterName, Temporary);
  1738.     IF Successful THEN
  1739.       BEGIN
  1740.         Assign(MeterFile, MeterName);
  1741.         {$I-} Reset(MeterFile);
  1742.         IF IOresult <> 0 THEN
  1743.           BEGIN
  1744.             Rewrite(MeterFile);
  1745.             Reset(MeterFile)
  1746.           END;
  1747.         Successful := IOresult = 0;
  1748.         IF Successful THEN
  1749.           BEGIN
  1750.             ReadLn(MeterFile, Limit);
  1751.             IF (IOresult <> 0) OR (Limit < 1) THEN Limit := 1;
  1752.             ReadLn(MeterFile, HighWater);
  1753.             IF (IOresult <> 0) OR (HighWater < 1) THEN HighWater := 0;
  1754.             NEW(UserList);
  1755.             UserList^.Next := NIL;
  1756.             User := UserList;
  1757.             Count := ReadUsers(MeterFile, User);
  1758.             GetDate(Today);
  1759.             User^.Name := D__MMM__YY(Today) + ' ' + GetUserName + ' ';
  1760.             User := UserList;
  1761.             IF Count < Limit
  1762.               THEN
  1763.                 BEGIN
  1764.                   Rewrite(MeterFile);
  1765.                   WriteLn(MeterFile, Limit);
  1766.                   IF Count >= HighWater THEN HighWater := Succ(Count);
  1767.                   WriteLn(MeterFile, HighWater);
  1768.                   REPEAT
  1769.                     WriteLn(MeterFile, User^.Name);
  1770.                     User := User^.Next
  1771.                   UNTIL User = NIL;
  1772.                   Close(MeterFile)
  1773.                 END
  1774.               ELSE
  1775.                 BEGIN
  1776.                   Close(MeterFile);
  1777.                   WriteLn('Too many users are using this function:', #10);
  1778.                   FSplit(MeterName, Directory, FileName, Ext);
  1779.                   REPEAT
  1780.                     Write(User^.Name:30, '':10);
  1781.                     FOR Ptr := 1 TO 3 DO
  1782.                       Delete(User^.Name, 1, Pos(' ', User^.Name));
  1783.                     Delete(User^.Name, Pos(' ', User^.Name), 255);
  1784.                     SendMessageToUser(User^.Name, GetUserName + ' is waiting to use ' + FileName);
  1785.                     User := User^.Next;
  1786.                     Count := Pred(Count)
  1787.                   UNTIL Count < 1;
  1788.                   Successful := FALSE
  1789.                 END;
  1790.             REPEAT
  1791.               User := UserList^.Next;
  1792.               DISPOSE(UserList);
  1793.               UserList := User
  1794.             UNTIL UserList = NIL
  1795.           END;
  1796.         Unlock(MeterName);
  1797.       END;
  1798.     IF NOT Successful THEN
  1799.       Write(#10, #13, 'Please try again later.  ')
  1800.   END;
  1801.  
  1802. (****************************************************************************)
  1803.  
  1804. PROCEDURE DecrementUserCount(MeterName : STRING);
  1805.  
  1806. { Removes the current user's name from a licence metering file. }
  1807.  
  1808. VAR  MeterFile  : TEXT;
  1809.      Count,
  1810.      HighWater,
  1811.      Limit     : INTEGER;
  1812.  
  1813.   BEGIN
  1814.     Lock(MeterName, Temporary);
  1815.     IF Successful THEN
  1816.       BEGIN
  1817.         Assign(MeterFile, MeterName);
  1818.         {$I-} Reset(MeterFile);
  1819.         IF IOresult = 0 THEN
  1820.           BEGIN
  1821.             ReadLn(MeterFile, Limit);
  1822.             IF IOresult <> 0 THEN Limit := 1;
  1823.             ReadLn(MeterFile, HighWater);
  1824.             IF IOresult <> 0 THEN HighWater := 0;
  1825.             NEW(UserList);
  1826.             UserList^.Next := NIL;
  1827.             User := UserList;
  1828.             Count := ReadUsers(MeterFile, User);
  1829.             User := UserList;
  1830.             Rewrite(MeterFile);
  1831.             WriteLn(MeterFile, Limit);
  1832.             WriteLn(MeterFile, HighWater);
  1833.             REPEAT
  1834.               IF User^.Next <> NIL THEN WriteLn(MeterFile, User^.Name);
  1835.               User := User^.Next
  1836.             UNTIL User = NIL;
  1837.             Close(MeterFile);
  1838.             REPEAT
  1839.               User := UserList^.Next;
  1840.               DISPOSE(UserList);
  1841.               UserList := User
  1842.             UNTIL UserList = NIL
  1843.           END;
  1844.         Unlock(MeterName)
  1845.       END
  1846.   END;
  1847.  
  1848. (****************************************************************************)
  1849.  
  1850. PROCEDURE Log(LogComment : STRING);
  1851.  
  1852. { Adds a line to a hidden log file.  The name of the file should be
  1853.   set into the environment variable LOG.  This procedure is generic -
  1854.   it does not require Netware to be loaded, }
  1855.  
  1856. VAR  LogFileName : DirStr;
  1857.      LogFile     : TEXT;
  1858.      Today       : Date;
  1859.  
  1860.   BEGIN
  1861.     LogFileName := getenv('LOG');
  1862.     IF LogFileName > ' '
  1863.       THEN Lock(LogFileName, Temporary)
  1864.       ELSE Successful := FALSE;
  1865.     IF Successful THEN
  1866.       BEGIN
  1867.         ChMod('-rs', LogFileName);
  1868.         Assign(LogFile, LogFileName);
  1869.         {$I-}
  1870.         Append(LogFile);
  1871.         IF IOresult <> 0 THEN ReWrite(LogFile);
  1872.         {$I+}
  1873.         IF IOresult <> 0
  1874.           THEN Successful := FALSE
  1875.           ELSE
  1876.             BEGIN
  1877.               GetDate(Today);
  1878.               WriteLn(LogFile, D__MMM__YY(Today):9, '│', time, ' ───────────── ', LogComment);
  1879.               Close(LogFile);
  1880.               ChMod('+rh', LogFileName)
  1881.             END;
  1882.         UnLock(LogFileName)
  1883.       END
  1884.   END;
  1885.  
  1886. (****************************************************************************)
  1887.  
  1888. PROCEDURE SetServerDateAndTime;
  1889.  
  1890. { This is of course Novell-specific, and it requires that the current
  1891.   user has Supervisor equivalence.  It sets the server time to the same
  1892.   as the DOS time at the current workstation.  It's up to you to alter
  1893.   the DOS time first.  Remember that the Novell login program sets DOS
  1894.   time the same as server time at each logon. }
  1895.  
  1896. TYPE Request = RECORD
  1897.                  Len     : WORD;
  1898.                  Func,
  1899.                  Year,
  1900.                  Month,
  1901.                  Day,
  1902.                  Hour,
  1903.                  Minute,
  1904.                  Second  : BYTE
  1905.                END;
  1906.      Reply   = RECORD
  1907.                  Len     : WORD
  1908.                END;
  1909.  
  1910. VAR  ReqBuf  : Request;
  1911.      RepBuf  : Reply;
  1912.      Today   : Date;
  1913.      Now     : TimeRec;
  1914.  
  1915.   BEGIN
  1916.     IF LockMode = 1 THEN
  1917.       BEGIN
  1918.         GetDate(Today);
  1919.         GetTime(Now);
  1920.         ReqBuf.Len    := 7;
  1921.         ReqBuf.Func   := $CA;
  1922.         ReqBuf.Year   := Today.Year;
  1923.         ReqBuf.Month  := Today.Month;
  1924.         ReqBuf.Day    := Today.Day;
  1925.         ReqBuf.Hour   := Now.Hour;
  1926.         ReqBuf.Minute := Now.Minute;
  1927.         ReqBuf.Second := Now.Second;
  1928.         RepBuf.Len    := 0;
  1929.         Netware($E3, Addr(ReqBuf), Addr(RepBuf))
  1930.       END
  1931.   END;
  1932.  
  1933. (****************************************************************************)
  1934.  
  1935. PROCEDURE DownServer(Name : String48);
  1936.  
  1937. { This downs the named server.  Use with care.  Supervisor equivalence
  1938.   required!!  Global BOOLEAN variable Successful holds the result. }
  1939.  
  1940. TYPE Request = RECORD
  1941.                  Len   : WORD;
  1942.                  Func,
  1943.                  Force : BYTE
  1944.                END;
  1945.  
  1946. VAR  ReqBuf       : Request;
  1947.      RepBuf       : WORD;
  1948.      CurrentPath  : PathStr;
  1949.      TempDrive    : CHAR;
  1950.  
  1951.   BEGIN
  1952.     Successful := FALSE;
  1953.     IF LockMode = 1
  1954.       THEN
  1955.         BEGIN
  1956.           GetDir(0, CurrentPath);
  1957.           TempDrive := 'D';
  1958.           { Loop through all drive letters to find one
  1959.             that is mapped to the required server. }
  1960.           WHILE (TempDrive <= 'Z')
  1961.           AND (ServerName(TempDrive) <> Name)
  1962.             DO Inc(TempDrive);
  1963.           IF TempDrive <= 'Z' THEN
  1964.             BEGIN
  1965.               {$I-}
  1966.               ChDir(TempDrive + ':');
  1967.               {$I+}
  1968.               IF IOresult = 0 THEN
  1969.                 BEGIN
  1970.                   ReqBuf.Len := 2;
  1971.                   ReqBuf.Func := $D3;
  1972.                   ReqBuf.Force := 1;
  1973.                   RepBuf := 0;
  1974.                   Netware($E3, Addr(ReqBuf), Addr(RepBuf));
  1975.                   ChDir(CurrentPath[1] + ':');
  1976.                   Successful := TRUE
  1977.                 END
  1978.             END
  1979.         END
  1980.   END;
  1981.  
  1982. (****************************************************************************)
  1983.  
  1984. BEGIN
  1985.   { This code checks whether Novell Netware is loaded, by attempting
  1986.     to change the lock mode.  The result is stored in LockMode. }
  1987.   Asm
  1988.     MOV AX, 0C601H  { Set lock mode to 1 }
  1989.     INT 21H
  1990.     MOV AX, 0C602H  { Get lock mode      }
  1991.     INT 21H
  1992.     MOV LockMode, AL
  1993.   End { Asm }
  1994. END.
  1995.  
  1996.