home *** CD-ROM | disk | FTP | other *** search
/ The World of Computer Software / World_Of_Computer_Software-02-387-Vol-3of3.iso / s / sltpu70a.zip / SLTPU.REF < prev    next >
Text File  |  1992-07-09  |  70KB  |  1,799 lines

  1.  
  2.                     Searchlight BBS Programmer's Library
  3.                              Function Reference
  4.                           Version 2.25, July, 1992
  5.  
  6.       (c) Copyright 1992 Searchlight Software. All Rights Reserved.
  7.  
  8.  
  9. -----------------------------------------------------------------------------
  10.       Unit: FILEDEF.TPU
  11. Depends On: Dos,Block,Multi
  12. -----------------------------------------------------------------------------
  13.  
  14. Description:
  15.  
  16. Contains procedures for opening the CONFIG, NODES and CHAT files, and 
  17. reading and writing records from these files.  The OPENFILES procedure 
  18. automatically initializes several key variables when opening the CONFIG 
  19. file, and is the recommended way of initializing any program.
  20.  
  21. FILEDEF.TPU also contains the type definitions used in SLBBS data files.
  22. Please see FILEDEF.REF for a listing of the type definitions.
  23.  
  24.  
  25. Procedures, Functions and Variables:
  26.  
  27.  
  28. type filetype = (CONFIGF,NODESF,CHATF);
  29.      fileset = set of filetype;
  30.  
  31. var  configpath: string[60] = '';
  32.  
  33. Function OpenFiles (arg: fileset): boolean;
  34.  
  35. The procedure OpenFiles, combined with the type definitions shown, will 
  36. initialize the system by opening CONFIG.SL2, and optionally NODES.SL2 and 
  37. CHAT.SL2.  Call OpenFiles with an argument of type fileset, depending on 
  38. which files you wish to open.  The return value is TRUE if the files were
  39. successfully opened, and FALSE if an error occured.
  40.  
  41. Example:
  42.  
  43.   if OpenFiles([CONFIGF,NODESF]) then begin
  44.     { main program here }
  45.   else
  46.   writeln('Error-- Could not find CONFIG file!');
  47.  
  48. OpenFiles needs to know the path to CONFIG.SL2 if it is not in the current 
  49. directory.  You can specify this path by setting the Configpath variable 
  50. before calling the OpenFiles procedure.  Programs typically obtain this 
  51. value from command line arguments.
  52.  
  53. Example:
  54.  
  55.   Configpath:=ParamStr(1);      { Assume 1st commandline parameter is path }
  56.   if OpenFiles([CONFIGF,NODESF]) ...
  57.  
  58. If 'Configpath' is not set and CONFIG.SL2 is not in the current directory,
  59. OpenFiles searches for an environment variable called SLBBS and uses that
  60. string as the path to CONFIG.SL2, if it exists.  Users can set up the
  61. environment with a DOS command such as "SET SLBBS=C:\BBS".
  62.  
  63. Only the CONFIG path need be specified.  OpenFiles automatically uses the
  64. 'Data Files Path' in the CONFIG file to locate the other files.
  65.  
  66. The CHAT.SL2 file is valid only for multiuser systems.  It is suggested that 
  67. you not attempt to open this file until you verify that more than 1 node is 
  68. operating.  This can be detected by examining the value of 'MaxNode' (see 
  69. below) after opening the CONFIG file.
  70.  
  71. Example:
  72.  
  73.   ok:=OpenFiles([CONFIGF,NODESF]);      { ok = boolean variable }
  74.   if ok and (maxnode>1)
  75.     then ok:=OpenFiles([CHATF]);
  76.  
  77. It is recommended that the CONFIG and NODES file be opened at the beginning 
  78. of every program and maintained for the life of the program.  The CHAT file 
  79. need not be opened unless it is required for the application.
  80.  
  81.  
  82.  
  83. var  cf: configtype;              { Global CONFIG Record }
  84.  
  85.      node,maxnode: integer;       { Global Node Info }
  86.  
  87.      isopen: array[CONFIGF..CHATF]
  88.        of boolean;
  89.  
  90. A global variable 'Cf' is declared in the Filedef unit and can be used 
  91. throughout an application to obtain information about the current system 
  92. configuration (see FILEDEF.REF for the record layout of this variable). 'Cf'
  93. is automatically initialized to the contents of the CONFIG.SL2 file when 
  94. that file is opened by OpenFiles.  
  95.  
  96. 'Node' and 'Maxnode' reflect the current node number and the maximum node 
  97. number for the system, respectively.
  98.  
  99. The 'Isopen' structure is an array of booleans that will let you determine 
  100. later in your program which files are open.  For example:
  101.  
  102.   if Isopen[CHATF]
  103.     then   { run chat functions }
  104.  
  105.  
  106. Procedure ReadCf;
  107. Procedure WriteCf;
  108.  
  109. These procedures read or write the global variable 'Cf' to the CONFIG.SL2 
  110. file.  'ReadCf' is not generally required since Cf is read into memory at 
  111. the start of the program.  If your program changes the value of a variable 
  112. in 'Cf', call 'WriteCf' to update it to disk.
  113.  
  114. Note: The CONFIG.SL2 file should always be private to any program which 
  115. manipulates it.  Therefore, there is no need to observe record locking 
  116. techniques when updating this file.
  117.  
  118.  
  119. Function NodeFileSize: integer;
  120. Procedure ReadNode (var n: nodetype; r: integer);
  121. Procedure WriteNode (var n: nodetype; r: integer);
  122.  
  123. Function 'NodeFileSize' returns the size in records of NODES.SL2 (which
  124. should always be equal to the value of 'Maxnode' plus 1).  Procedure
  125. 'ReadNode' reads a variable of type 'Nodetype' from the NODES.SL2 file at
  126. the given record number ('r') which should be between 0 and Maxnode.  You
  127. can use this procedure to obtain information about the other nodes on a
  128. multiuser system.  If you wish to update the nodes file, 'Writenode' will
  129. write a node record back to disk.
  130.  
  131.  
  132. Procedure CloseAllFiles;
  133.  
  134. This procedure closes the CONFIG, NODES and CHAT file if they are open.  It 
  135. should be called by a program before terminating.  Note that in spite of its 
  136. name, 'CloseAllFiles' does not close the USER file, or any open SUBBOARD or 
  137. FILE DIRECTORY files; be sure to close those files, too.
  138.  
  139.  
  140.  
  141.  
  142. -----------------------------------------------------------------------------
  143.       Unit: BLOCK.TPU
  144. Depends On: Dos,Multi
  145. -----------------------------------------------------------------------------
  146.  
  147. Description:
  148.  
  149. The BLOCK unit contains a number of low-level procedures used by other units 
  150. in the library.  Only a few of its procedures and functions are generally 
  151. needed directly by application programs.
  152.  
  153.  
  154. Procedures, Functions and Variables:
  155.  
  156.  
  157.      BlockFileType = record        { ram file info }
  158.        filevar: file;              { dos file var }
  159.        open: boolean;              { set if file is open }
  160.        lock: integer;              { file lock count }
  161.        recsize: word;              { record size }
  162.        offset: longint;            { offset to 1st record }
  163.        header: fileheader;         { header info }
  164.        hlock: integer;             { header locked? }
  165.      end;
  166.  
  167. The 'BlockFileType' variable is used by the MESSAGE, USERS, MEMBERS and DIR 
  168. units as file variables for the Searchlight data files managed by these 
  169. units.  We present the above type definition for your information.
  170. Application programs should NOT need to declare variables of this type, 
  171. although you should be aware that the variable 'UserFile' (USERS.TPU) and 
  172. the file components of 'MainSub' (MESSAGES.TPU) and 'MainDir' (DIR.TPU) are 
  173. declared as type 'BlockFileType'.  If necessary, you can access information 
  174. such as the record size, open status, and locked status of these files in 
  175. the record fields; however, do not attempt to change any of these fields
  176. yourself.
  177.  
  178.  
  179. Function SizeInRecs (var f: blockfiletype): longint;
  180.  
  181. This function returns the size in records of a given 'BlockFileType'.  Valid 
  182. parameters for this function are variables such as 'UserFile' (Searchlight 
  183. user file) or 'Mainsub.Headerf' (Main subboard header file).  The function 
  184. returns the TOTAL number of blocks in the file, which includes deleted 
  185. records.  This function can be used by programs that process files 
  186. sequentially; be sure to ignore deleted records (deleted records will have 
  187. the value $FF in the first byte of the record).
  188.  
  189.  
  190. Procedure ReadBlockfile (var f: blockfiletype; r: longint; p: pointer);
  191. Procedure WriteBlockfile (var f: blockfiletype; r: longint; p: pointer);
  192.  
  193. These two functions read and write any record from any Searchlight data file 
  194. that is currently open; for example, the Userfile.  These functions are low 
  195. level in nature and will only rarely be used by application programs.  The 
  196. program must insure that the pointer value passed contains enough room to 
  197. read a variable of the correct size from the file.
  198.  
  199. Example:
  200.  
  201.   ReadBlockFile(Userfile,17,@User);
  202.  
  203. Reads the 17th record from the user file into variable 'User', which is
  204. presumably of type 'UserType' (although no type checking is performed).
  205. See LockFile and UnlockFile below for more information.
  206.  
  207.  
  208.  Function LockFile (var f:blockfiletype): boolean;
  209. Procedure UnLockFile (var f:blockfiletype);
  210.  
  211. These procedures implement DOS level file locking, allowing programs to 
  212. update shared files on multiuser systems without corruption.  The 'LockFile' 
  213. and 'UnlockFile' procedures are rarely required by application programs, 
  214. since all of the high-level functions provided in this library (ie. 
  215. 'AddUser' in USERS.TPU, 'MsgPost' in MESSAGE.TPU) automatically lock and 
  216. unlock files as needed.
  217.  
  218. The only time you need to call these procedures directly is if your program 
  219. needs to update a record in a shared file (ie. any file other than the 
  220. CONFIG file) directly.  The correct way to do this is:
  221.  
  222.   (1) LOCK the specified file
  223.   (2) READ the record from the file that requires updating
  224.   (3) UPDATE the record in memory
  225.   (4) WRITE the updated record back to disk
  226.   (5) UNLOCK the file
  227.  
  228. It is important that the total time that a file is LOCKed be as short as 
  229. possible, since LOCKing a file prevents other nodes from updating it. You 
  230. should also be aware that if you call 'LockFile' to lock a file and that 
  231. file is already locked, 'LockFile' will wait up to 45 seconds for the file 
  232. to become unlocked before returning with a value of FALSE to indicate that 
  233. the lock attempt failed.  Programs should always check the value returned by 
  234. 'LockFile' and report an error if it is false.
  235.  
  236. This example updates the number of times read ('Rd') variable in a message 
  237. header in the HEADER file of a subboard.  Assume that we already know the 
  238. value of the header's record number (RecNum) and that 'Header' is of type 
  239. 'HeaderType'.  'ReadBlockFile' is defined as above.
  240.  
  241.   if LockFile(Mainsub.Headerf) then begin
  242.     ReadBlockFile(Mainsub.Headerf,RecNum,@Header);
  243.     Inc(Header.Rd);
  244.     WriteBlockFile(Mainsub.Headerf,RecNum,@Header);
  245.     UnLockFile(Mainsub.Headerf);
  246.   end;
  247.  
  248. Notice that the updating procedure is skipped if the lock fails (this will 
  249. rarely happen unless a serious error occurs) and that the file remains 
  250. locked for just long enough as is necessary to perform the update.  Also, 
  251. notice that the record is read into memory before being updated. This is 
  252. required, even if the record had already been in memory, since another node 
  253. might have updated it between the time it was first read and the time the 
  254. file was locked.
  255.  
  256. IMPORTANT -- Do not try to update they key (indexed) field of any record
  257. simply by rewriting the record!  This includes the 'Name' field of user,
  258. member and directory records, the date field of directory records, etc.
  259. Updating key fields requires insertion of a new record and deletion of the
  260. old record, via the insert/delete commands provided for the various files.
  261.  
  262. Calls to 'Lockfile' and 'Unlockfile' for the same file can be nested without
  263. ill effects.  For example, if this code fragment is executed:
  264.  
  265.   if Lockfile(Userfile) then begin
  266.     { instructions here }
  267.     if Lockfile(Userfile) then begin
  268.       { more instructions here }
  269.       UnlockFile(Userfile);
  270.     end;
  271.     UnlockFile(Userfile);
  272.   end;
  273.  
  274. Only the first call to LockFile actually locks the user file, and only the
  275. last call to UnlockFile actually unlocks the user file.  The nested calls
  276. are ignored.  This behavior allows procedures which call the Lock and
  277. Unlock functions to work correctly, even if those procedures are called
  278. while the file is already locked.
  279.  
  280. On a single user system-- even a system where a multiuser version of
  281. Searchlight is in use, but only one node is active-- calls to LockFile and 
  282. UnLockFile have NO EFFECT.  Therefore, programs should ALWAYS call these 
  283. functions when updating a file; they will be effectively ignored if no file 
  284. locking is necessary.
  285.  
  286.  
  287.  
  288.  
  289. -----------------------------------------------------------------------------
  290.       Unit: GENERAL.TPU
  291. Depends On: Dos,Filedef
  292. -----------------------------------------------------------------------------
  293.  
  294. Description:
  295.  
  296. The GENERAL unit contains some helpful functions for processing SLBBS time 
  297. and date types, as well as some general purpose functions which are used by 
  298. the other units in the library and may be called by application programs.
  299.  
  300.  
  301. Procedures, Functions and Variables:
  302.  
  303.  
  304. Function Min (x,y:integer): integer;
  305. Function Max (x,y:integer): integer;
  306.  
  307. These are just the classic Min and Max functions; Min returns the smaller of 
  308. its two arguments, and Max returns the larger.  Useful in a variety of 
  309. situations where limits need to be enforced.
  310.  
  311.  
  312. Procedure Upstr (var s: string);           { Conv string to uppercase }
  313. Procedure TruncSpaces (var s: string);     { Strip trailing spaces }
  314. Procedure StripSpaces (var s: string);     { Strip leading & trailing spaces }
  315.  
  316. String conversion procedures.  'Upstr' converts a string to all uppercase.  
  317. 'TruncSpaces' strips a string of any trailing spaces, and 'StripSpaces' 
  318. strips both leading and trailing spaces.
  319.  
  320.  
  321. Procedure Dosdate (var d: datetype);
  322. Procedure Dostime (var t: timetype);
  323.  
  324. These functions return the current date and time in the format used by 
  325. Searchlight data files.
  326.  
  327. Example:
  328.  
  329.   Dosdate(header.date);
  330.   Dostime(header.time);
  331.  
  332. Assuming 'Header' is a record of type 'Headertype', these two function
  333. calls will initialize the header to the current date and time.
  334.  
  335.  
  336. Function SecOffset: longint;
  337. Function ElapsedFrom (t: longint): longint;
  338.  
  339. Used together, these functions let you time events to an accuracy of about 2 
  340. seconds.  Call 'SecOffset' at the beginning of an event and save its value 
  341. in a variable.  Call 'ElapsedFrom' with that saved value later to find out 
  342. how many seconds have passed since the call to SecOffset.
  343.  
  344. Example:
  345.  
  346.   starttime:=SecOffset;
  347.   repeat
  348.     { time consuming function here }
  349.   until ElapsedFrom(starttime)>=30;     { loop for 30 seconds }
  350.  
  351.  
  352. Function StrDate (d: datetype): string;
  353.  
  354. This function returns the date in the argument as an 8 character string in 
  355. the format 'MM-DD-YY', or 'DD/MM/YY' if European date format is selected in 
  356. Searchlight.  
  357.  
  358.  
  359. Function DateDiff (d1,d2: datetype): integer;
  360.  
  361. The 'DateDiff' function returns the number of days between two dates, 'd1' 
  362. and 'd2'.  If 'd1' is a later date than 'd2', then the result will be 
  363. positive.  Otherwise, the result is negative.  This function is useful for 
  364. comparing dates in messages or user files to a target date.
  365.  
  366.  
  367. Function TimeDiff (t1,t2: timetype): integer;
  368.  
  369. Returns the difference between 2 times, in minutes.  This function works 
  370. correctly even if midnight has passed between the start time and the current 
  371. time.
  372.  
  373.  
  374. Procedure Hash (s: string; var r: pwtype);
  375.  
  376. This procedure transforms a password string into a 3 byte password hashcode
  377. (pwtype) used in the USER file and file directory files.  You can use this
  378. procedure to change file or user passwords or to install passwords for new
  379. users or files.
  380.  
  381. The input string ('S') should be an all-caps string with no leading or
  382. trailing spaces (embedded spaces are OK).
  383.  
  384. Example:
  385.  
  386.   Hash('SHIPYARD',User.Passwd);    { Change user's password to SHIPYARD }
  387.  
  388.  
  389. Function Nullpw (r: pwtype): boolean;
  390.  
  391. This function takes a password hashcode as input, and returns TRUE if the
  392. password is blank.  You can use this to check for a blank password before
  393. asking a user to enter a password.
  394.  
  395. Example:
  396.  
  397.   if not NullPw(User.Passwd)
  398.     then { input password from user }
  399.     else { skip password input }
  400.  
  401. Note: Blank passwords simply contain 3 bytes of binary zero.  To create a
  402. blank password, simply fill the password (Pwtype) with zero.  Example:
  403.  
  404.   Fillchar(User.Passwd,sizeof(pwtype),0);     { blank user's password }
  405.  
  406.  
  407. Function CheckPasswd (passwd: pwtype; passtr: string): boolean;
  408.  
  409. This function can be used to check whether a password string which was input 
  410. by a user matches a stored encoded password.  You can use this function
  411. to check passwords in the USER file or in directory files.  The result is
  412. true if the password matches, false otherwise.
  413.  
  414. Note that password strings should be all capital letters and should not
  415. contain any leading or trailing spaces, although embedded spaces are allowed.
  416.  
  417. Example:
  418.  
  419.   input(passtr);     { input password from user }
  420.   upstr(passtr);         { see GENERAL.TPU }
  421.   stripspaces(passtr);   { see GENERAL.TPU }
  422.   if CheckPasswd(DirRecord.passwd,passtr)
  423.     then writeln('Password correct')
  424.     else writeln('Incorrect password!');
  425.  
  426.  
  427.  
  428.  
  429. -----------------------------------------------------------------------------
  430.       Unit: USERS.TPU
  431. Depends On: Block,Filedef,Tree
  432. -----------------------------------------------------------------------------
  433.  
  434. Description:
  435.  
  436. This unit contains the main procedures required to access the Searchlight 
  437. USER file, including procedures to search for, insert, and delete users.
  438.  
  439.  
  440. Procedures, Functions and Variables:
  441.  
  442.  
  443. var User: Usertype;
  444.  
  445. This global variable of type 'Usertype' should always be used to store 
  446. information about the current user.  It is not initialized; to do that, you 
  447. should call
  448.   
  449.   Readuser(user,cf.curruser)
  450.  
  451. after opening the User file.  It is recommended that you use this global
  452. variable exclusively to process the current user.  If you program requires
  453. access to other user records, declare other variables of type UserType and
  454. use them locally.
  455.  
  456.  
  457. var UserFile: BlockFileType;
  458.  
  459. This is the file variable that contains the user file.  It is normally not 
  460. required to refer to this variable, except when performing low level 
  461. operations such as file lock and unlock (see BLOCK unit).
  462.  
  463.  
  464. Function OpenUserFile: boolean;
  465. Procedure CloseUserFile;
  466.  
  467. The user file needs to be explicitly opened before any other procedures in 
  468. this unit may be used, and should be closed when the program terminates. 
  469. Open the user file after the CONFIG file has been opened; 'OpenUserFile' 
  470. will use the data path stored in CONFIG.  The function returns TRUE if the 
  471. user file is successfully opened, FALSE if there was an error.
  472.  
  473. Example:
  474.  
  475.   if OpenFiles([CONFIGF,NODESF]) and OpenUserFile then begin
  476.     { main program here}
  477.     CloseUserFile;
  478.     CloseAllFiles;
  479.   end;
  480.  
  481. It is recommended that the user file be opened if you plan to save or
  482. delete messages.
  483.  
  484.  
  485. Procedure ReadUser (var u: usertype; n: longint);
  486. Procedure WriteUser (var u: usertype; n: longint);
  487.  
  488. These functions read or write a record from the User file (which must be 
  489. open).  'U' contains a record of usertype, and 'N' is the desired record 
  490. number.  Calling ReadUser and WriteUser is identical to calling the low 
  491. level functions 'BlockRead' and 'BlockWrite' (BLOCK.TPU) except that these 
  492. functions are more convenient and provide type checking.
  493.  
  494. 'WriteUser' should be used with care; see the record locking techniques in
  495. BLOCK.TPU for more information.  Do not attempt to update the user name
  496. by rewriting the record with a different name.
  497.  
  498.  
  499. Procedure Usearch ( key: string;           { name to look for }
  500.                     var urec: usertype;    { resulting user record }
  501.                     var upos: longint);    { resulting record number }
  502.  
  503. Procedure 'Userch' searches for a record in the user file by name.  On 
  504. entry, 'Key' contains the name of the user you are searching for.  'Key' 
  505. should be all-uppercase, and should not contain any leading or trailing 
  506. spaces.
  507.  
  508. On return, if the user is found, 'Urec' contains the user record for the 
  509. user and 'Upos' contains the record number where the user was found in the 
  510. User file.  If the user could not be found, Upos equals zero.
  511.  
  512. Example:
  513.  
  514.   Usearch('SYSOP',User,UserRec);
  515.   if (UserRec=0) then writeln('Cannot find SYSOP Account!');
  516.  
  517.  
  518. Procedure AddUser (var newuser: usertype; var result: longint);
  519.  
  520. This procedure adds a user to the user file.  'Newuser' is a user record 
  521. which should be initialized with the user name (MUST be all-caps, with no 
  522. leading or trailing spaces!) and other information (ie. location, phone 
  523. number, date of first login, etc).  The return value 'Result' contains the 
  524. record number where the new user record was stored if the function was 
  525. successful.  If the function fails, Result = 0.
  526.  
  527. The most common reason why this function might fail is if the user you are 
  528. trying to add is already on file.
  529.  
  530. Note: be sure to initialize the Passwd field of a user record before adding
  531. the user; otherwise, the user may have a random password.  See procedure
  532. 'Hash' in GENERAL.TPU for more information.
  533.  
  534.  
  535. Function DeleteUser (key: string): boolean;
  536.  
  537. Deletes the given username from the user file.  As with Usearch and AddUser, 
  538. the name must be all caps with no leading or trailing spaces.
  539.  
  540. The result of the function is TRUE if the user was successfully deleted, or 
  541. FALSE if some error occured (such as if the user could not be found).
  542.  
  543.  
  544. Function UserFileRoot: longint;
  545.  
  546. This function returns the root, or first record number, of the binary tree 
  547. that's used to structure the user file.
  548.  
  549. Programs which need to process users in alphabetical order, or which wish to 
  550. perform their own searches of the userfile, should use this function to 
  551. obtain the record number of the tree root.  From this record, the pointers 
  552. 'Leaf.Left' and 'Leaf.Right' (part of the UserType data structure) form a 
  553. binary tree, with 'Left' pointing to all users with names which come before 
  554. the current user alphabetically, and 'Right' pointing to all users who come 
  555. after.  (The userfile should not, under normal circumstances, contain any 
  556. duplicate records).
  557.  
  558. The best way to access the user tree structure is through a recursive 
  559. traversal procedure.  See the sample programs for examples of this 
  560. technique.  
  561.  
  562.  
  563.  
  564.  
  565. -----------------------------------------------------------------------------
  566.       Unit: SUBLIST.TPU
  567. Depends On: DOS,General,Filedef,Block,Tree,Users
  568. -----------------------------------------------------------------------------
  569.  
  570. Description:
  571.  
  572. This unit contains procedures for reading the Searchlight subboard list or 
  573. file directory list (ie. SUBBOARD.SL2 or FILEDIR.SL2) into memory, and 
  574. accessing its contents through your program.
  575.  
  576. The method Searchlight and the SUBLIST.TPU unit use to access the subboard 
  577. or file directory list is to read the entire file into memory as a linked 
  578. list, close the file, and access all information through memory pointers.  
  579. The functions in this unit allow you to read in the subboard list or file 
  580. directory list and provide data types and pointers for accessing the 
  581. contents in memory.  Only one list can be loaded at any time.
  582.  
  583.  
  584. Procedures, Functions and Variables:
  585.  
  586.  
  587. type SubListPtr = ^SubListType;
  588.  
  589.      SubListType = record      { ram subboard/filedir list }
  590.        fname: string[8];       { name }
  591.        path: string[60];       { path to HDR/MSG/MBR or DIR file }
  592.        name: string[40];       { long description }
  593.        access: integer;        { required access level }
  594.        attrib: attribset;      { required attributes }
  595.        visible: boolean;       { visible on lists? }
  596.        recnum: longint;        { record # in SL2 file }
  597.        next: SubListPtr;       { next subboard or filedir }
  598.  
  599.        case integer of
  600.          1: (              { Subboards }
  601.          postattrib: attribset; { attributes required to post }
  602.          subsysop: string[25];  { subboard sysop }
  603.          echomail: boolean;     { not currently supported }
  604.          lastread: longint);    { not currently supported }
  605.  
  606.          2: (              { File Directories }
  607.          filepath: string[38];  { path to upload/download files }
  608.          readonly,
  609.          writeonly: boolean;    { readonly/writeonly status }
  610.          free: word;            { free files (K) }
  611.          value: integer;        { file value (x10) }
  612.          dirpath: boolean;      { set if filepath<>path }
  613.          drive: integer);       { drive where files reside (1=A:, etc.) }
  614.  
  615.      end;
  616.  
  617.  
  618. The 'SubListType' and 'SubListPtr' types define the data type used to store 
  619. records from either SUBBOARD.SL2 or FILEDIR.SL2 in memory.  Typically, 
  620. application programs will access the list by setting a pointer of type 
  621. 'SubListPtr' equal to the linked list root, then following Pointer^.Next to 
  622. access each successive record.
  623.  
  624. Notice that SubListType is a variant record, which holds data for either 
  625. subboard entried or file directory entries.  It is up to the program to know 
  626. which variant is currently active, and not try to access data in the wrong 
  627. one.
  628.  
  629. 'RecNum' provides the record number of the field within the SUBBOARD.SL2 or
  630. FILEDIR.SL2 file.  This value should NOT be used to modify the file.  It is
  631. provided for instances where you need to have a unique numerical identifier
  632. for each subboard (for example, if you are writing a conversion procedure
  633. for a BBS system which uses subboard numbers instead of names).
  634.  
  635.  
  636. type  SystemType = (Subboards,FileDirs);
  637. const SubListRoot: SubListPtr = Nil;
  638. var   SubListSize: integer;
  639.  
  640. Type 'SystemType' is used by the 'SubListInit' procedure to specify whether 
  641. subboards or file directories are to be loaded.  After execution of 
  642. SubListInit, 'SubListRoot' and 'SubListSize' are initialized to the root of 
  643. the subboard list and its size in entries.
  644.  
  645.  
  646. Procedure SubListInit (system: systemtype);
  647.  
  648. This is the procedure that initializes the subboard list.  The parameter is 
  649. either 'Subboards' or 'Filedirs'.  
  650.  
  651. Example:
  652.  
  653.   SubListInit(Subboards);      { Initialize subboard list }
  654.  
  655. When called, the SUBBOARD.SL2 file is read into memory (memory is allocated 
  656. dynamically from the heap) and the SubListRoot and SubListSize variables are 
  657. initialized.
  658.  
  659. There is no need to explicitly deallocate the subboard list, as it is
  660. automatically deallocated by an exit procedure when the program terminates.
  661.  
  662.  
  663. Procedure SubListOrder (filename: string; system: systemtype);
  664.  
  665. This procedure rearranges the items in the subboard or file directory list 
  666. according to a custom ordering file, such as MAIN.SUB or MAIN.DIR.  This 
  667. procedure should be called AFTER SubListInit.  Pass the full filename to the 
  668. SUB/DIR file, and the system type (ie. Subboards or FileDirs).  The result 
  669. is that the linked list is updated to be in the specified order.  
  670.  
  671. If SubListOrder is not called, the linked list will be in alphabetical order 
  672. by default.
  673.  
  674. Note: when ordering the SUBBOARD list, the MAIL and BULLETIN subboards are 
  675. always included in the list, regardless of whether they appear in the 
  676. SUB/DIR file.
  677.  
  678.  
  679. Function ListIndex (s: string): SubListPtr;
  680.  
  681. This function searches for a given entry (subboard name or filedir name) in 
  682. the linked list, and if found, returns a pointer to its record.  If the item 
  683. is not found, the function returns a NIL pointer.  The search string ('S') 
  684. should be all uppercase, and should not contain leading or trailing spaces.
  685.  
  686. Be aware that this function will attempt to match partial names as in 
  687. Searchlight BBS.  For example, a call to ListIndex('FIDO') may return a 
  688. pointer to a subboard called 'FIDONET', if that subboard exists and one 
  689. called 'FIDO' does not.  For critical applications, it is best to test the 
  690. name of the returned subboard explicity.
  691.  
  692. Example:
  693.  
  694.   var p: sublistptr;
  695.  
  696.   p:=ListIndex('MAIL');
  697.   if (p^.fname='MAIL') 
  698.     then { ok } 
  699.     else { error } ;
  700.  
  701.  
  702. Sequential processing of the subboard/filedir list can be accomplished by 
  703. setting a pointer to the SubListRoot value initially, then following the 
  704. Next pointers until the end of the list is reached.  Here is an example of a 
  705. function which will print out the names of all the subboards available:
  706.  
  707.   var p: sublistptr;
  708.  
  709.   begin
  710.     p:=SubListRoot;
  711.     while (p<>Nil) do begin
  712.       writeln(p^.Fname);
  713.       p:=p^.next;
  714.     end;
  715.   end;
  716.  
  717.  
  718. Function SubIsReadable (s: sublistptr; var a: accesstype): boolean;
  719.  
  720. Determines whether the given subboard (indicated by a pointer to its
  721. linked list memory location) is accessible to a user with the given
  722. access levels.  Typically, this function is used while processing a
  723. subboard list to determine which subboards a user has permission to read.
  724.  
  725. Example:
  726.  
  727.   if SubIsReadable(p,user.access)
  728.     then { process subboard }
  729.     else { skip subboard }
  730.  
  731. The actual formula used by this function is as follows:
  732.  
  733.     SubIsReadable:=((s^.access<=a.msglevel) and (s^.attrib-a.attrib=[]))
  734.       or (a.msglevel>=254);
  735.  
  736. To check whether a user has POST access to a particular subboard, you can
  737. use a similar formula, except comparing the user's attributes to the
  738. subboard's 'PostAttrib' field.  Notice the use of set subtraction in the
  739. expression (s^.attrib-a.attrib=[]) as a means of determining whether the
  740. user's attributes meet the attribute requirements of the subboard.
  741.  
  742.  
  743.  
  744.  
  745. -----------------------------------------------------------------------------
  746.       Unit: MEMBERS.TPU
  747. Depends On: General,Block,Filedef,Tree,Message
  748. -----------------------------------------------------------------------------
  749.  
  750. Description:
  751.  
  752. Contains procedures for accessing the MEMBER files associated with 
  753. subboards, including procedures for searching, adding and deleting member 
  754. records.
  755.  
  756. The functions in MEMBERS.TPU work with the 'MainSub' variable declared in 
  757. MESSAGE.TPU, and require that the subboard member file be open (via a call 
  758. to OpenSub in MESSAGE.TPU).
  759.  
  760.  
  761. Procedures, Functions and Variables:
  762.  
  763.  
  764. var Member: MembType;                { Global current member rec }
  765.     MembRec: longint;                { Global member record number }
  766.  
  767. These global variables should be used to store the member record and record 
  768. number for the current user in the current subboard.  It is recommended that 
  769. these variables not be used to store intermediate results when traversing a 
  770. member file (use local variables for that purpose).  Member and MembRec are 
  771. not initialized when you open a subboard; however, they can easily be 
  772. initialized with one function call:
  773.  
  774.   MSearch(user.name,member,membrec);
  775.  
  776. ...Assuming USER is initialized to contain the current user's information.
  777.  
  778.  
  779. Procedure ReadMember (var m: membtype; n: longint);
  780. Procedure WriteMember (var m: membtype; n: longint);
  781.  
  782. These procedures read and write variables of type 'Membtype' from the MEMBER 
  783. file associated with the currently opened subboard.  When writing, be sure 
  784. to observe file locking techniques as outlined in BLOCK.TPU.
  785.  
  786.  
  787. Procedure Msearch ( key: string;           { name to look for }
  788.                     var mrec: membtype;    { resulting user record }
  789.                     var mpos: longint);    { resulting pointer }
  790.  
  791. This procedure searches for a name in the member file.  If the name is 
  792. found, it loads 'Mrec' with the member record, and sets 'Mpos' to the record 
  793. number where the member record was found.  If the name is not found, then 
  794. 'Mpos' contains a value of zero.
  795.  
  796.  
  797. Procedure AddMember (var user: usertype;
  798.                      var member: membtype;
  799.                      var result: longint);
  800.  
  801. This procedure adds a member to the member file; it requires a USERTYPE 
  802. record containing the name of the user to be added.  'Member' need not be 
  803. initialized.  The result contains the record number where the new member was 
  804. stored, or 0 if an error occured.
  805.  
  806. 'AddMember' automatically initializes the fields of the member record with 
  807. the date of first login, and sets the high message pointer to zero.
  808.  
  809. If you wish to add a member who is not a user, create a dummy 'Usertype' 
  810. variable an initialize it with the desired member name (be sure to use all 
  811. capital letters).
  812.  
  813.  
  814.  Function DeleteMember (key: string): boolean;
  815.  
  816. Deletes a given member from the member file, returing TRUE if the delete was 
  817. successful, or FALSE if an error occured.
  818.  
  819.  
  820.  Function MemberFileRoot: longint;
  821.  
  822. This function returns the root record number of the binary tree structure 
  823. within a member file, and can be used to process the records of a member 
  824. file in alphabetical order.  Searching member files is similar to searching 
  825. the user file.  Please see 'UserFileRoot' in USERS.TPU and the example 
  826. programs for more information.
  827.  
  828.  
  829. Procedure UpdateLastRead (n: longint);
  830.  
  831. This procedure updates the last read pointer for the current user record 
  832. (ie. the user record defined by the global variables 'Member' and 
  833. 'Membrec').  'N' is the new value to be assigned.  This procedure performs 
  834. file locking as required for multiuser access.
  835.  
  836.  
  837.  
  838.  
  839. -----------------------------------------------------------------------------
  840.       Unit: MESSAGE.TPU
  841. Depends On: General,Block,Filedef,Sublist
  842. -----------------------------------------------------------------------------
  843.  
  844. Description:
  845.  
  846. MESSAGE.TPU contains basic procedures required to open and manage subboards.
  847. A major part of this unit is the description of the data structure used to 
  848. hold message texts in RAM; programs which read or write messages to 
  849. Searchlight subboards must be familiar with this structure since it is the 
  850. format which the post and retrieval commands expect.  
  851.  
  852. This unit requires that the subboard list be loaded (SubListInit in 
  853. SUBLIST.TPU), and all functions that deal with reading or writing data from 
  854. subboards of course require that the subboard be opened first.  Only one 
  855. subboard at a time can be opened with the procedures in this unit.
  856.  
  857.  
  858. Procedures, Functions and Variables:
  859.  
  860.  
  861. const maxlinlen = 76;   { default max line length }
  862.       maxlines = 400;   { max. lines per message }
  863.  
  864. type strptr = ^string;
  865.      msgptr = ^msgtype;       { RAM Message Format }
  866.      msgtype = record
  867.                  msglen: integer;  { length of msg in lines }
  868.                  msglin: array [1..maxlines]
  869.                    of strptr;      { pointers to lines }
  870.                end;
  871.  
  872. This is the data structure in which message texts are manipulated in memory.  
  873. Most procedures in this library expect a variable of type 'MsgPtr', which is 
  874. a pointer to a record containing the message length in lines, followed by an 
  875. array of pointers to the message lines.  The lines themselves are stored in 
  876. memory as standard Turbo Pascal string types; conversion between this data 
  877. type and the format used to store messages on disk (including data 
  878. compression) is handled automatically by the library procedures.
  879.  
  880. Variables of type 'MsgPtr' need to be initialized, allocated and disposed of 
  881. in a strict manner using the functions below.
  882.  
  883.  
  884. Procedure ClearMsg (msg: msgptr);
  885.  
  886. 'ClearMsg' should be called with a new variable of type 'MsgPtr' right after 
  887. allocating heap space for that pointer; it creates a message with 0 lines. 
  888. This function MUST be called if you are creating a new message in memory.
  889.  
  890. Example:
  891.  
  892.   var msg: msgptr;
  893.  
  894.   New(msg);        { allocate heap space for message }
  895.   ClearMsg(msg);   { clear message to zero lines }
  896.  
  897.  
  898. Procedure AddLine (var msg: msgptr; s: string);
  899.  
  900. This procedure adds a line to the given message, and can be used to build 
  901. messages in memory from external sources (such as a disk file, or an editor) 
  902. which will later be saved to a Searchlight message base.  Be sure that the 
  903. 'Msg' variable has been allocated and cleared (as above) before you call 
  904. this function.  The line added must be 76 characters or less in length.
  905.  
  906. Programs which require editing of messages in memory should be aware that 
  907. the 'AddLine' function always allocates 78 bytes to each MsgLin variable in 
  908. the message; this insures that there is enough room for lines to be edited 
  909. if desired.  If your editing procedure needs to insert or delete lines on 
  910. its own, you should use the GetMem and FreeMem procedures as follows:
  911.  
  912.    GetMem(msg^.msglin[x],maxlinlen+2);    { x = line number }
  913.    FreeMem(msg^.msglin[x],maxlinlen+2); 
  914.  
  915. It is not necessary to use these functions unless you need to perform 
  916. extensive editing in memory.  Otherwise, create the message by adding one 
  917. line at a time to it, and clear it with the 'DisposeMsg' function.
  918.  
  919.  
  920. Procedure DisposeMsg (var msg: msgptr);
  921.  
  922. This procedure disposes a message completely, including deallocating the 
  923. space used by all the message lines and the space used by the 'Msgptr' 
  924. itself.  Be sure to call this function when you are done with a message so 
  925. that its memory can be reused.
  926.  
  927.  
  928. type SubFileSet = record     { Files for message system }
  929.        headerf,                 { header file }
  930.        msgf,                    { texts file }
  931.        memberf: BlockFileType;  { members file }
  932.        Name: string[8];
  933.        Subinfo: subtype;
  934.      end;
  935.  
  936.      subfilestype = (HEADERF,MSGF,MEMBERF);
  937.      subfilesset = set of subfilestype;
  938.  
  939. const allfiles: subfilesset=[HEADERF,MSGF,MEMBERF];
  940.  
  941. var MainSub: SubFileSet;
  942.  
  943. These definitions form the in-memory data structures needed to declare and 
  944. open a subboard.  'MainSub' always contains information about the currently 
  945. open subboard, and is used implicitly by the message storage and retrieval 
  946. commands.
  947.  
  948.  
  949. Function OpenSub (name: string; var f: SubFileSet; files: subfilesset): boolean;
  950.  
  951. The 'OpenSub' function attempts to open a subboard; it returns TRUE if the 
  952. subboard was opened, or FALSE if an error was encountered and the subboard 
  953. could not be initialized.
  954.  
  955. The 'Name' parameter contains the 1 to 8 character subboard name, which 
  956. should be all uppercase and have no leading or trailing spaces.  'F' should 
  957. always contain the value 'MainSub'.  'Files' contains the constant 
  958. 'AllFiles' (defined above) or a subset of it, if you do not wish to open all 
  959. 3 of the files associated with a subboard.
  960.  
  961. Example:
  962.  
  963.   if OpenSub('GENERAL',MainSub,AllFiles) then begin
  964.     { processing here }
  965.   end
  966.   else writeln('Could not open subboard!');
  967.  
  968. If the attempt to open the subboard is successful, the 'Name' and 'Subinfo' 
  969. fields in the 'SubFileSet' parameter are initialized with the name of the 
  970. subboard and the contents of the subboard's header information.
  971.  
  972. In general, the 'AllFiles' parameter should be used if you intend to do 
  973. general processing on a subboard.  If you only intend to process the header 
  974. file, or if you do not need access to the Member file, you can save time by 
  975. opening only the files you need.
  976.  
  977. Example:
  978.  
  979.   if OpenSub('GENERAL',Mainsub,[HEADERF]) then ...    { Open Headerfile Only }
  980.   if OpenSub('GENERAL',Mainsub,[HEADERF,MSGF]) ...    { Header & Message Files }
  981.  
  982. You MUST open all subboard files if you plan to save messages!
  983.  
  984.  
  985. Procedure ReadSubInfo (var m: subfileset; var s: subtype);
  986.  
  987. Reads a subboard's information (header) record into memory.  The variable 
  988. 'Mainsub.Subinfo' is automatically initialized to contain the information 
  989. found in a subboard's header file when the header file is opened.  You can 
  990. refer to this variable (which is of type 'Subtype') for information about 
  991. the current subboard.  Be aware, however, that in multiuser systems, 
  992. information can change if the value is not refreshed from the actual disk 
  993. file; therefore, read it from disk before using any critical value.
  994.  
  995. Example:
  996.  
  997.   ReadSubInfor(Mainsub,Mainsub.Subinfo);
  998.   writeln('There are ',Mainsub.Subinfo.Messages,' Messages on this subboard.');
  999.  
  1000.  
  1001. Procedure CloseSub (var f: SubFileSet);
  1002.  
  1003. This procedure closes the open files in a subboard.  Be sure to call this 
  1004. when you are done processing a particular subboard, or when your program 
  1005. ends.
  1006.  
  1007. Example:
  1008.  
  1009.   CloseSub(Mainsub);
  1010.  
  1011.  
  1012. Function FindHeader (var h: headertype; ix: ixtype; id,rec: longint): longint;
  1013.  
  1014. 'FindHeader' searches for the header record of a message given the message 
  1015. number or UID number of the message.  If the header is found, the function 
  1016. returns its record number in the header file (of the currently open 
  1017. subboard) and reads the header information into the 'Headertype' parameter.
  1018.  
  1019. The 'Ix' parameter is a constant which has the value 'Seq' or 'Uid'.  Use 
  1020. the 'Seq' value if you are searching for a message number, and use 'Uid' if 
  1021. you are searching for a UID value (ie. the internal numbers used for 
  1022. threading purposes).  Please see the documentation file accompanying this 
  1023. library for a more thourough discussion of message reading techniques.
  1024.  
  1025. The 'Id' value is a long integer containing either the message number or UID 
  1026. number you are searching for.  Note that if the header cannot be found, the 
  1027. function returns a result of 0.
  1028.  
  1029. The 'Rec' parameter is optional; it can be used to indicate the record 
  1030. number where you expect the header to be located.  Pass a value of 0 if you 
  1031. do not know this information.  This parameter is included for instances 
  1032. where you know the target record number, and saves you the trouble of 
  1033. writing an IF-THEN statement to handle those cases.  If the record number is 
  1034. passed, the record can be located much more quickly.
  1035.  
  1036. Examples:
  1037.  
  1038.   { r = longint variable, header = headertype variable }
  1039.  
  1040.   r:=FindHeader(header,Seq,1,0);
  1041.  
  1042.   If r<>0 after this function call, 'Header' will contain the header of 
  1043.   message #1 on the current subboard, and R will contain its record number 
  1044.   in the header file.  If r=0, then there is no message #1 on this subboard.
  1045.  
  1046.   r:=FindHeader(header,Seq,100,RecNum);
  1047.  
  1048.   Searches for message #100.  If 'RecNum' is nonzero, the search is 
  1049.   expediated by looking directly to the record number indicated (for 
  1050.   example, this number might have been retrieved from the 'NextSeqRec' or
  1051.   'NextMailRec' field of another header).
  1052.  
  1053.   r:=FindHeader(header,Uid,header.replyto,0);
  1054.  
  1055.   Assuming 'header' contains the header of a message which is a reply, this
  1056.   function call locates the message to which that message is a reply.  
  1057.   Notice that 'Header.Replyto', as well as other thread values in the 
  1058.   header, represent Uid values rather than message number (Seq) values.
  1059.  
  1060.  
  1061. Procedure Unpackmsg (rec: longint; var msg: msgptr; newmsg: boolean);
  1062.  
  1063. This procedure retrieves the text of a message from the subboard message 
  1064. file on disk into memory (in the 'Msg' variable).  'Rec' contains the record 
  1065. number of the first block of text in the message.  The value for 'Rec' 
  1066. should always be obtained from the 'Txt' field of a valid header record.
  1067.  
  1068. The boolean variable 'Newmsg' should be set to TRUE if 'Msg' represents an 
  1069. unitialized variable of type 'Msgptr'.  If 'Msg' has been initialized (ie. 
  1070. allocated with New(), and cleared with ClearMsg()), then this value can be 
  1071. set to FALSE, and the text from the MSG file will append any text already in 
  1072. memory in the Msg variable.  Whether or not 'Msg' is initialized before 
  1073. calling Unpackmsg, you MUST dispose of the message via DisposeMsg() when you 
  1074. are done using it.
  1075.  
  1076. The 'Unpackmsg' procedure automatically determines whether a message has 
  1077. been compressed and uncompresses and unpacks the text automatically.
  1078.  
  1079. Example:
  1080.  
  1081.   Assume that 'Header' contains a valid header record from the header file:
  1082.  
  1083.   Unpackmsg(header.txt,msg,true);
  1084.  
  1085.   This procedure call will read the message's text into the memory variable 
  1086.   'Msg'.  You can then access the text line by line through the 'Msg' 
  1087.   variable (see above for the record layout).  Note that it is possible for
  1088.   a message to have 0 lines of text.
  1089.   
  1090.  
  1091. Function IsSubop (var user: usertype): boolean;
  1092.  
  1093. This boolean function takes a Usertype record and returns TRUE if that user 
  1094. is the SubSysop of the current subboard ('MainSub'); otherwise, it returns 
  1095. FALSE.
  1096.  
  1097. Example:
  1098.  
  1099.   if IsSubop(user)
  1100.     then { grant subsysop priviledge }
  1101.  
  1102.  
  1103. Function Owns (var h: headertype; var user: usertype): boolean;
  1104.  
  1105. This function takes the header of a message and a Usertype as input, and 
  1106. determines whether the user owns the message (ie. the user is the person who 
  1107. posted the message).  This function simply compares the name in the message 
  1108. 'From' field to the username.
  1109.  
  1110. Example:
  1111.  
  1112.   if Owns(header,user)
  1113.     then { allow user to edit message }
  1114.  
  1115.  
  1116.  
  1117.  
  1118. -----------------------------------------------------------------------------
  1119.       Unit: POST.TPU
  1120. Depends On: Filedef,Block,Users,Members,Message,Kill
  1121. -----------------------------------------------------------------------------
  1122.  
  1123. Description:
  1124.  
  1125. The POST unit contains the complete high-level function calls necessary to 
  1126. post a message on the currently opened subboard.  This unit also provides a 
  1127. quick function for adding members to subboards and for making duplicate 
  1128. (carbon copy) messages.
  1129.  
  1130.  
  1131. Procedures, Functions and Variables:
  1132.  
  1133.  
  1134. Function MsgPost (msg: msgptr;              { text to post }
  1135.                   var header: headertype;   { header info }
  1136.                   original: longint;        { original, if any }
  1137.                   verbose: boolean;         { print 'Saving...' ? }
  1138.                   setuid: boolean):         { compute uid? }
  1139.                     longint;                { result record number }
  1140.  
  1141. The 'MsgPost' function is a complete high level function call for posting a 
  1142. message on the current subboard.  This function performs all of the 
  1143. functions necessary for posting a message including allocating file space, 
  1144. indexing the header file, and maintaining the linked lists and threads 
  1145. between messages.  Note that all 3 files of the current subboard must be
  1146. open before calling MsgPost.
  1147.  
  1148. Before calling MsgPost, you must:
  1149.  
  1150.   (1) Have the text of the message you want to post in memory ('Msg')
  1151.   (2) Initialize the header information
  1152.   (3) If the message is a reply, know the UID number or message number
  1153.       of the original message
  1154.  
  1155. Text should be contained in the 'Msg' variable, which is of type 'Msgptr' as 
  1156. described in the MESSAGE.TPU unit.  You can build the text portion of the 
  1157. message using the ClearMsg and AddLine procedures from MESSAGE.TPU.
  1158.  
  1159. The message header (type HeaderType) should be initialized by filling the 
  1160. header with binary zeros, then initializing the 'From', 'Touser', 'Subj', 
  1161. 'Time', and 'Date' fields accordingly.  No other fields in the Header need 
  1162. be initialized before calling MsgPost.
  1163.  
  1164. MsgPost automatically maintains reply threads.  If the message is a reply, 
  1165. the 'Original' parameter should contain the UID number of the original 
  1166. message (this can be obtained from Header.Id[UID] in the header of the 
  1167. original).  If you know the message number of the original message instead 
  1168. of its UID number, you can pass that value as a negative number (ie.
  1169. -1*Header.ID[Seq]).  It is preferable to pass the UID number if possible 
  1170. since the UID number is immune to any renumbering which might occur, and it 
  1171. also allows preservation of threads when networking (for more information,
  1172. please see the DOC file).  If your message is not a reply, pass 0 as the 
  1173. value of this parameter.
  1174.  
  1175. In general, the 'SetUID' parameter should be passed as TRUE to have MsgPost 
  1176. automatically compute the UID value of the message and store it in the 
  1177. message header.  If you are importing messages from a network and the UID 
  1178. value of a message is known, you should store it in 'Header.Id[Uid]' and 
  1179. pass FALSE for the SetUID parameter.  This will allow the message to retain 
  1180. its original UID value and provide for correct thread maintenance.  See the 
  1181. DOC file for more information.
  1182.  
  1183. If 'Verbose' is TRUE, MsgPost will print the message 'Saving...' as it saves 
  1184. the message.  Otherwise, no such message is displayed.
  1185.  
  1186. If applicable, you may set the 'FFrom' (forwarded-from), 'Rd' (times read), 
  1187. 'Prot' (purge protect) and 'Attribute' (Echomail attributes) fields of the 
  1188. header before calling MsgPost.
  1189.  
  1190. 'MsgPost' returns the record number in the header file where the header was 
  1191. stored as a result.  It also returns the Header itself initialized with the 
  1192. message number of the new message, and other pointer information.  Note that 
  1193. all messages posted with MsgPost are always assigned the next highest 
  1194. sequential message number on the current subboard.
  1195.  
  1196. MsgPost automatically compresses the text before posting the message, if
  1197. the compression option is selected for the current subboard.
  1198.  
  1199. Be sure to dispose of the text of the message ('DisposeMsg()') when you are 
  1200. done with it.
  1201.  
  1202. Example:
  1203.  
  1204.   { In this example, we will read the text from a file called 'MYFILE.TXT'
  1205.     and post it as a message from GUEST to SYSOP. Assume that a subboard
  1206.     has been opened. }
  1207.  
  1208.   { prepare message text }
  1209.   new(msg);
  1210.   clearmsg(msg);                     { msg = msgptr }
  1211.   assign(input,'MYFILE.TXT');
  1212.   reset(input);
  1213.   while not eof(input) do begin
  1214.     readln(input,str);               { str = string }
  1215.     AddLine(msg,str);
  1216.   end;
  1217.   close(input);
  1218.  
  1219.   { prepare header }
  1220.   fillchar(header,sizeof(header),0);
  1221.   header.from:='GUEST';
  1222.   header.touser:='SYSOP';
  1223.   header.subj:='Text of Myfile.txt';
  1224.   dosdate(header.date);                 { from GENERAL.TPU }
  1225.   dostime(header.time);                 { from GENERAL.TPU }
  1226.  
  1227.   { post the message }
  1228.   result:=MsgPost(msg,header,0,true,true);      { result = longint }
  1229.  
  1230.   Disposemsg(msg);
  1231.  
  1232.  
  1233.  
  1234. Procedure MsgDup (msgtxt: longint;           { msg to duplicate }
  1235.                   var header: headertype;    { new header }
  1236.                   original: longint);        { original message }
  1237.  
  1238. This procedure call allows you to make carbon copies of messages that have 
  1239. been posted with MsgPost.  It is recommended that this function be called 
  1240. only immediately after MsgPost.
  1241.  
  1242. Calling this function will result in 2 messages on the subboard having 
  1243. different headers but identical text.  Note that Searchlight BBS performs 
  1244. this function only in the MAIL subboard for the purpose of providing carbon 
  1245. copies of one message to several users.
  1246.  
  1247. To make a carbon copy of a message, first create a new message header.  The 
  1248. new header should contain any changes (usually in the 'Touser' field) that 
  1249. the copy requires.
  1250.  
  1251. 'MsgTxt' is the record number in the MSG file where the text of the message 
  1252. to duplicate begins.  You can retrieve this value from 'Header.Txt' of the 
  1253. original copy of the message.
  1254.  
  1255. Finally, if the message is a reply, pass the 'Original' value as in MsgPost.
  1256.  
  1257. MsgDup duplicates a message without making another copy of its text.  
  1258. Therefore, it is fast and space efficient.  You can call MsgDup as many 
  1259. times as you need.
  1260.  
  1261. MsgDup cannot be used to copy a message from one subboard to another.  To do 
  1262. that, you must read the header and message into memory, open the new 
  1263. subboard, and save the message with MsgPost.
  1264.  
  1265.  
  1266.  
  1267. Procedure MakeMember (username: string);
  1268.  
  1269. 'MakeMember' is a quick procedure which adds the given username to the 
  1270. member file of the current subboard, if the user is not already a member.  
  1271. The 'Username' should be all caps, with no leading or trailing spaces.
  1272.  
  1273. It is recommended that this procedure be called before posting any messages 
  1274. on the MAIL subboard, to insure that the user to whom the message is 
  1275. addressed is actually a member (and therefore has a mailbox).
  1276.  
  1277. Example:
  1278.  
  1279.   MakeMember(header.touser);
  1280.   result:=MsgPost(msg,header,0,true,true);
  1281.  
  1282. Note that as an alternative to forcing the user to be a member before 
  1283. posting MAIL, you could check whether the user is a member (see MEMBER.TPU) 
  1284. before allowing the message to be posted.  It is not necessary to force the 
  1285. message recipient to be a member except on the MAIL subboard.
  1286.  
  1287.  
  1288.  
  1289.  
  1290. -----------------------------------------------------------------------------
  1291.       Unit: KILL.TPU
  1292. Depends On: Filedef,Block,Message,Members,Users
  1293. -----------------------------------------------------------------------------
  1294.  
  1295. Description:
  1296.  
  1297. Contains procedures for deleting messages on the current subboard.
  1298.  
  1299.  
  1300. Procedures, functions and variables:
  1301.  
  1302.  
  1303. Procedure KillMsgID (msgid: longint);
  1304.  
  1305. This procedure deletes any message from the current subboard.  'Msgid' is 
  1306. the message number of the message you want to delete (which can be obtained 
  1307. from Header.Id[Seq] in the message's header, if not otherwise available).
  1308.  
  1309. KillMsgID automatically maintains all of the threading, linked lists, etc. 
  1310. associated with the message.
  1311.  
  1312. Example:
  1313.  
  1314.   KillMsgID(100);      { delete message #100 }
  1315.  
  1316.  
  1317. Procedure AutoTrim;
  1318.  
  1319. This procedure checks whether the number of messages on the current subboard 
  1320. exceeds the maximum number of messages set by the Sysop in the SETUP 
  1321. program.  If it does, and if 'Auto purge old messages' is set to Yes, then 
  1322. this procedure deletes the oldest unprotected messages on the subboard until 
  1323. the total number of messages equals the maximum number.
  1324.  
  1325. AutoTrim can be called subsequent to posting a number of new messages on a 
  1326. subboard, to insure that old messages are purged if required.
  1327.  
  1328.  
  1329.  
  1330.  
  1331. -----------------------------------------------------------------------------
  1332.       Unit: CHAT.TPU
  1333. Depends On: Dos,Filedef,Block
  1334. -----------------------------------------------------------------------------
  1335.  
  1336. Description:
  1337.  
  1338. Contains functions to control the Searchlight BBS CHAT file.
  1339.  
  1340. This unit assumes that the CONFIG, NODES and CHAT file have already been 
  1341. opened via the OpenFiles procedure (FILEDEF.TPU).
  1342.  
  1343. The Chat system maintains a queue of four incoming messages for each node on 
  1344. the system in the CHAT.SL2 file, which is maintained by the procedures in 
  1345. this unit.
  1346.  
  1347. Chat functions should only be used in a multiline environment (this can be 
  1348. determined by checking that 'maxnode' has a value greater than 1).
  1349.  
  1350.  
  1351. Procedures, functions and variables:
  1352.  
  1353.  
  1354. Function MsgAvail: boolean;
  1355.  
  1356. This function returns true if an incoming message is available in our chat 
  1357. queue, false if no message is available.
  1358.  
  1359. Example:
  1360.  
  1361.   if MsgAvail
  1362.     then { display incoming message }
  1363.  
  1364.  
  1365. Function GetMsg (var from,s: string): integer;
  1366.  
  1367. This function returns the next available message.  The 'From' field is a 
  1368. string representation of the sender's name, 'S' contains the message itself.  
  1369. The function result is the node number where the message originated.
  1370.  
  1371. Example:
  1372.  
  1373.   if MsgAvail then begin
  1374.     msgfrom:=GetMsg(from,s);
  1375.     writeln('Message from ',from,' on node ',msgfrom,':');
  1376.     writeln(s);
  1377.   end;
  1378.  
  1379.  
  1380. Function MsgFull (sendnode: integer): boolean;
  1381.  
  1382. This function checks if another node's message queue is full. If the queue 
  1383. for the node indicated by 'sendnode' is full, the function returns TRUE, 
  1384. otherwise it returns FALSE.
  1385.  
  1386. Message queues can hold up to 4 messages.  If the user on a node to whom 
  1387. messages are sent does not read those messages faster than they are being 
  1388. sent, the queue can fill up.  Use this function to determine whether a queue 
  1389. is full before sending a message.
  1390.  
  1391. Example:
  1392.  
  1393.   if not MsgFull(n)
  1394.     then { send message to node N }
  1395.  
  1396.  
  1397. Function SendMsg (sendnode: integer; from,s: string): boolean;
  1398.  
  1399. This function sends a message to another node.  'Sendnode' is the node 
  1400. number, 'From' is the name of the sender (up to 25 characters, usually the 
  1401. current user name), and 'S' is the message, up to 73 characters.  
  1402.  
  1403. The function result is TRUE if the message was sent, or FALSE if it was not 
  1404. sent because the receiver's queue is full.  When a message is not sent, the 
  1405. application can either discard it, or offer the user a chance to wait a few 
  1406. moments and send again.  
  1407.  
  1408. Example:
  1409.  
  1410.   If SendMsg(1,user.name,'Hello, World')
  1411.     then writeln('Message send successfully')
  1412.     else writeln('Message was not sent, queue was full');
  1413.  
  1414.  
  1415. Procedure ClearMsgQueue;
  1416.  
  1417. Clears the incoming message queue for the current node; in other words,
  1418. clears all messages to us.  You can use this procedure when you want to 
  1419. discard all incoming messages.
  1420.  
  1421.  
  1422. Function Tell (name,from,s: string): boolean;
  1423.  
  1424. This is a higher level function that sends a message to a user by name, 
  1425. checking the NODES file to see whether that user is online.  If the named 
  1426. user is on another node, the message is sent and the function returns TRUE. 
  1427. If the user is not online, or if the user's chat queue is full, no message 
  1428. is sent and the return is FALSE.  
  1429.  
  1430.  
  1431. Procedure SetChatStat (b: byte);
  1432.  
  1433. Sets the chat status for our node.  Byte values are as follows:
  1434.  
  1435.     0: 'Using Main Program'
  1436.     1: 'Logging In'
  1437.     2: 'In Chat Mode'
  1438.     3: 'Chatting with SYSOP'
  1439.     4: 'Reading Messages'
  1440.     5: 'Entering a Message'
  1441.     6: 'Using Files System'
  1442.     7: 'Using Sysop Program'
  1443.    50: 'Uploading a File'
  1444.    51: 'Downloading a File'
  1445.    52: 'Using a Door Program'
  1446.    53: 'Logging Off'
  1447.    99: 'Not Available'
  1448.  
  1449.  
  1450. Function GetChatStat (node: integer): byte;
  1451.  
  1452. Reads the chat status of another node.  Values are the same as above.
  1453.  
  1454. Note that values 50 and above indicate that the user is away from the main
  1455. program (or not available); CHAT programs should refrain from initiating a
  1456. conversation with a user who's chat status is 50 or over.
  1457.  
  1458.  
  1459. Function ChatStatus (b: byte): string;
  1460.  
  1461. Returns a string representation of the given chat status.  Strings returned 
  1462. are as above.
  1463.  
  1464.  
  1465.  
  1466.  
  1467. -----------------------------------------------------------------------------
  1468.       Unit: DIR.TPU
  1469. Depends On: Dos,Block,Filedef,Users,Tree,LList,Sublist
  1470. -----------------------------------------------------------------------------
  1471.  
  1472. Description:
  1473.  
  1474. Contains procedures and functions for accessing Searchlight file directory 
  1475. files, as well as general procedures useful for file processing.
  1476.  
  1477. Most of the procedures in this unit assume that the file directory list has 
  1478. been initialized (via a call to SubListInit).
  1479.  
  1480.  
  1481. Procedures, functions and variables:
  1482.  
  1483.  
  1484. type DirFileSet = record
  1485.        dirf: blockfiletype;
  1486.        dirinfo: Dirheader;
  1487.        name: string[8];
  1488.        path: string[60];
  1489.        drive: integer;
  1490.      end;
  1491.  
  1492. var MainDir: dirfileset;
  1493.  
  1494. Variable 'MainDir' defines the current directory.  Use this variable in 
  1495. conjunction with the functions below which require a 'DirFileSet' parameter.
  1496.  
  1497.  
  1498. Function OpenDir (name: string; var f: DirFileSet): boolean;
  1499.  
  1500. Opens a file directory (*.SL2) file.  'Name' is the name of the directory to 
  1501. open, which should be the exact name in all-caps with no spaces.  If the 
  1502. open is successful, the function returns TRUE.
  1503.  
  1504. Note that OpenDir requires that the list of file directories has already 
  1505. been loaded into memory via SubListInit (SUBLIST.TPU) and that the requested 
  1506. directory is part of the list currently in memory.
  1507.  
  1508. Example:
  1509.  
  1510.   if OpenDir('UPLOADS',MainDir) then begin
  1511.     { process uploads dir }
  1512.   end
  1513.   else writeln('Could not open UPLOADS dir');
  1514.  
  1515.  
  1516. Procedure CloseDir (var f: DirFileSet);
  1517.  
  1518. Closes the current directory.  Be sure to call this procedure to close the 
  1519. data file when you are done processing it.
  1520.  
  1521. Example:
  1522.  
  1523.   CloseDir(MainDir);
  1524.  
  1525.  
  1526. Procedure ReadDir (var d: dirtype; r: longint);
  1527. Procedure WriteDir (var d: dirtype; r: longint);
  1528.  
  1529. These procedures can be used to read and write records (type 'Dirtype') from 
  1530. the current open file directory, when the record number is known.
  1531.  
  1532. 'ReadDir' is generally of use when processing multiple files in order by 
  1533. filename, date, or physical record order (if processing in physical order, 
  1534. care must be taken to avoid processing deleted records, which are marked by 
  1535. an $FF value in Leaf.Status).  Use the DirFileRoot function (below) or the 
  1536. ListRoot function (LLIST.TPU) for an appropriate starting point for 
  1537. processing a directory in indexed order.
  1538.  
  1539. Use 'WriteDir' to update records.  Be sure to observe proper file locking as 
  1540. described in BLOCK.TPU.  Do not alter the filename or date when writing a 
  1541. record directly back to disk.  If the filename or date must be changed, this 
  1542. can be handled by deleting the record and re-inserting it (see below).
  1543.  
  1544. Example:
  1545.  
  1546.   for i:=1 to SizeInRecs(Maindir.Dirf) do begin
  1547.     ReadDir(dirrecord,i);       { dirrecord = dirtype }
  1548.     if Dirrecord.Leaf.status<>$FF
  1549.       then { process record }
  1550.       else { record is deleted }
  1551.   end;
  1552.  
  1553.  
  1554. Procedure Dsearch ( key: string;           { filename to look for }
  1555.                     var drec: dirtype;     { resulting record }
  1556.                     var dpos: longint);    { resulting pointer }
  1557.  
  1558. 'DSearch' searches for a particular file by name in the current directory.  
  1559. The input parameter (Key) should contain the filename in all caps, with no 
  1560. spaces, drive letters, or directory paths.
  1561.  
  1562. On return, 'Drec' and 'Dpos' are set to the file's directory entry and 
  1563. record number.  If the record is not found, then Dpos=0.
  1564.  
  1565.  
  1566. Procedure AddFile (var newentry: dirtype; var result: longint);
  1567.  
  1568. This procedure call adds a new file to the current directory.  You should 
  1569. clear the 'Dirtype' parameter and then initialize it with the proper 
  1570. filename, date, description and file length (note that the length is 
  1571. expressed as the number of 128 byte blocks in the file, not the size in 
  1572. bytes).  The 'Id' field in Dirtype should contain the record number of the 
  1573. user who uploaded the file, if applicable.
  1574.  
  1575. This procedure does not check whether the file actually exists.  It is up to 
  1576. the application program to make that determination before adding the record.  
  1577. It is permissible to add a record for a file that does not exist, if that is 
  1578. desired.
  1579.  
  1580. Be sure to initialize the password field of the record before inserting it.
  1581. See procedure Hash in GENERAL.TPU for more information.
  1582.  
  1583. Example:
  1584.  
  1585.   fillchar(newentry,sizeof(dirtype),0);
  1586.   with newentry do begin
  1587.     name:='MYFILE.ZIP';
  1588.     descrip:='Private file for the sysop';
  1589.     length:=GetFileSize('MYFILE.ZIP') div 128;    { see below }
  1590.     id:=cf.curruser;
  1591.     fillchar(passwd,sizeof(passwd),0);   { Blank password field }
  1592.     Dosdate(date);                       { DosDate from GENERAL.TPU }
  1593.   end;
  1594.   AddFile(newentry,result);
  1595.  
  1596.  
  1597. Function DeleteFile (key: string): boolean;
  1598.  
  1599. This function deletes the given entry from the Searchlight file directory.  
  1600. 'Key' should contain the exact filename in all caps.  The result is TRUE if 
  1601. the file was successfully deleted, FALSE if the file was not found or could 
  1602. not be deleted because of a disk error.
  1603.  
  1604. This procedure does not affect the physical file, only its directory entry.  
  1605. If you wish to delete the actual file, you must do so yourself.
  1606.  
  1607. Example:
  1608.  
  1609.   if DeleteFile('MYFILE.ZIP')
  1610.     then writeln('Deleted successfully')
  1611.     else writeln('Error deleting file.');
  1612.  
  1613.  
  1614. Function DirFileRoot: longint;
  1615.  
  1616. Returns the record number of the root of the binary tree in the current 
  1617. directory file.  Can be used to process the file directory in alphabetical 
  1618. order; the procedure is much the same as for processing the User file.  See 
  1619. example programs for more information.
  1620.  
  1621.  
  1622.  
  1623. The following functions may be useful to application programs when 
  1624. processing user input, etc.
  1625.  
  1626.  
  1627. Function IsWild (var s: string): boolean;
  1628.  
  1629. This function returns TRUE if the input filename contains any wildcard 
  1630. characters or if it contains two or more filenames separated by a space.  If 
  1631. the input consists of a single filename, FALSE is returned.
  1632.  
  1633. It is up to application programs to parse wildcard entries, if such 
  1634. processing is desired.
  1635.  
  1636.  
  1637. Function Legal (filename: string): boolean;
  1638.  
  1639. Returns TRUE if the given filename is a legal DOS filename and contains no 
  1640. drive letters, paths, or wildcards.  Otherwise, returns FALSE.  This 
  1641. function is useful for checking user input filenames before attempting to 
  1642. access the file, to protect against accessing illegal filenames.
  1643.  
  1644.  
  1645. Function GetFileSize (filename: string): longint;
  1646.  
  1647. Returns the size of a given disk file in bytes.  This function accesses the 
  1648. file itself on disk, not the Searchlight directory entry.  'Filename' may be 
  1649. any filename including a path, if required.
  1650.  
  1651. The result is the file size in bytes or 0 if the file does not exist.
  1652.  
  1653.  
  1654.  
  1655.  
  1656. -----------------------------------------------------------------------------
  1657.       Unit: TREE.TPU
  1658. Depends On: Block,Filedef
  1659. -----------------------------------------------------------------------------
  1660.  
  1661. Description:
  1662.  
  1663. Contains low level procedures and functions associated with maintenance of 
  1664. binary tree data structures.
  1665.  
  1666. The only function in this unit which is of use to application programs is:
  1667.  
  1668.  
  1669. Function TreeSize (var f: blockfiletype): longint;
  1670.  
  1671. This function returns the number of records allocated to the binary tree,
  1672. for binary tree oriented files (such as the User file or subboard member
  1673. files).  This tells you the actual number of active records in the file.
  1674.  
  1675. Example:
  1676.  
  1677.   writeln('There are ',TreeSize(Userfile),' Active users on file.');
  1678.  
  1679. Notice the difference between TreeSize and SizeInRecs (BLOCK.TPU): TreeSize
  1680. returns the number of records in use, whereas SizeInRecs returns the number
  1681. of records physically allocated in the file, including deleted records and
  1682. records which have never been used.  TreeSize is valid only for binary tree
  1683. oriented files (Userfile, Member files [MAINSUB.MEMBERF] and Dir files).
  1684.  
  1685.  
  1686.  
  1687.  
  1688. -----------------------------------------------------------------------------
  1689.       Unit: LLIST.TPU
  1690. Depends On: General,Block,Filedef,Tree
  1691. -----------------------------------------------------------------------------
  1692.  
  1693. Description:
  1694.  
  1695. Contains low level procedures associated with the maintenance of linked 
  1696. lists in directory files.
  1697.  
  1698. The only procedure in this unit which may be needed in application programs 
  1699. is:
  1700.  
  1701.  
  1702. Function ListRoot (var f: blockfiletype): longint;
  1703.  
  1704. Returns the root of the date-sorted linked list for a file directory.  This 
  1705. procedure can be used to process files in date sorted order.  The return 
  1706. value is always the most recently uploaded file in the current directory.
  1707.  
  1708. To traverse the date tree, follow 'Leaf.Next' and 'Leaf.Last' pointers in 
  1709. the directory records.
  1710.  
  1711. Example:
  1712.  
  1713.   root:=ListRoot(Maindir.Dirf);
  1714.  
  1715.  
  1716.  
  1717.  
  1718. -----------------------------------------------------------------------------
  1719.       Unit: MULTI.TPU
  1720. Depends On: Dos
  1721. -----------------------------------------------------------------------------
  1722.  
  1723. Description:
  1724.  
  1725. Contains procedures which help optimize the performance of a program when
  1726. running under multitasking operating systems.  You can call the procedures
  1727. in this unit from any program, and they will work regardless of what
  1728. operating system is running.
  1729.  
  1730.  
  1731. Procedures, functions and variables:
  1732.  
  1733.  
  1734. type OSType = (SingleUser,DoubleDos,DesqView,WinStd,WinEnh,OS2);
  1735.  Var Environment: OSType;
  1736.  
  1737. The 'Environment' variable is initialized automatically by MULTI.TPU's
  1738. startup code, and can be examined to determine whether your program is
  1739. running under one of the supported multitasking operating systems. Note:
  1740. 'WinStd' indicates Windows running in either Standard or Real mode.
  1741. 'WinEnh' indicates Windows running in 386 enhanced mode.
  1742.  
  1743. Example:
  1744.  
  1745.   case Environment of
  1746.     DoubleDos: writeln('We are running under DoubleDos');
  1747.     DesqView: writeln('We are running under DesqView');
  1748.     WinStd: writeln('We are running in standard or real mode Windows');
  1749.     WinEnh: writeln('We are running in Windows Enhanced 386 mode');
  1750.     OS2: writeln('We are running in OS/2');
  1751.     SingleUser: writeln('We are not running under a known multitasker');
  1752.   end;
  1753.  
  1754. Note: If Environment=Singleuser it does not mean that no other nodes are
  1755. active; it only means that no multitasker that this until can detect is
  1756. running.  The system could be running on a LAN or under a multitasker which
  1757. is not detected.  Always check the 'MaxNode' variable (FILEDEF.TPU) to
  1758. determine the number of active nodes.
  1759.  
  1760.  
  1761. Procedure Slice;                 { slice to next process }
  1762.  
  1763. The 'Slice' procedure is very important for programs that contain loops
  1764. which wait for keystrokes or other external events.  When running under a
  1765. multitasker, calling 'Slice' causes the multitasker to begin running the
  1766. next program immediately, thus allocating a minimum of CPU time to your
  1767. program.  This is desirable when your program is idle for long periods of
  1768. time.  'Slice' is usually called in keyboard input loops, but can also be
  1769. called from any loop which waits for something to happen (such as a loop
  1770. that waits for a modem carrier).
  1771.  
  1772. Example:
  1773.  
  1774.   while not KeyPressed do Slice;
  1775.     { waste time in an efficient manner until a keystroke occurs }
  1776.  
  1777.  
  1778. Procedure Pause (n: integer);    { delay for n 55-ms intervals }
  1779.  
  1780. This procedure pauses for a given number of clock ticks.  One clock tick
  1781. is equal to approximately 55 milliseconds.  'Pause' calls the 'Slice'
  1782. procedure repeatedly until the required time has elapsed, thus your program
  1783. uses very little CPU time during the pause.
  1784.  
  1785. 'Pause' should be called anytime your program needs to pause; it works
  1786. regardless of whether a multitasking OS is loaded.
  1787.  
  1788. Example:
  1789.  
  1790.   Pause(18);   { Pause for about 1 second }
  1791.  
  1792. Note that the accuracy of this procedure is plus or minus 1 clock tick.
  1793.  
  1794.  
  1795.  
  1796.  
  1797. -----------------------------------------------------------------------------
  1798. (c) Copyright 1992 Searchlight Software.  All Rights Reserved.
  1799.