home *** CD-ROM | disk | FTP | other *** search
/ Programmer 7500 / MAX_PROGRAMMERS.iso / PASCAL / TBTREE.ZIP / FILES.PAS < prev    next >
Encoding:
Pascal/Delphi Source File  |  1988-07-25  |  26.3 KB  |  629 lines

  1. (* TBTree13             Copyright (c)  1988            Dean H. Farwell II    *)
  2.  
  3. unit Files;
  4.  
  5. (*****************************************************************************)
  6. (*                                                                           *)
  7. (*                 F I L E  H A N D L I N G  R O U T I N E S                 *)
  8. (*                                                                           *)
  9. (*****************************************************************************)
  10.  
  11. (* This unit is used to create files and to manipulate the existence BITMAPs
  12.    for the associated files.  There are currently four types of files:
  13.    BITMAP, DATA, INDEX and LLIST.  The data files are used to hold user data.
  14.    The INDEX files can be used to hold an index for a given DATA file.  The
  15.    BITMAP files are used to show which physical or logical records are in use.
  16.    In the case of INDEX files, the associated BITMAPs show  which physical
  17.    records are in use.  In the case of DATA files, the associated BITMAPS show
  18.    which logical records are in use.  This will allow deleted records to be
  19.    reused for new records.  The LLIST files are for holding logical record
  20.    lists which take up more than one disk page and must be stored on
  21.    disk (or in the page buffer.  The is no BITMAPs associated with LLIST
  22.    files.                                                                    *)
  23.  
  24. (* Version Information
  25.  
  26.    Version 1.1 - No Changes
  27.  
  28.    Version 1.2 - No Changes
  29.  
  30.    Version 1.3 - No Changes                                                  *)
  31.  
  32.  
  33.  
  34. (*\*)
  35. (*////////////////////////// I N T E R F A C E //////////////////////////////*)
  36.  
  37. interface
  38.  
  39. uses
  40.     FileBuff,
  41.     FileDecs,
  42.     Math,
  43.     Numbers,
  44.     Page,
  45.     Strings;
  46.  
  47.  
  48. (* This routine will take the input file name and will strip off any extension
  49.    which exists.  It will then add on the file extension which applies to the
  50.    type passed in as a parameter.                                            *)
  51.  
  52. procedure FixFileName(var fName : FnString;
  53.                       fType : FileTypes);
  54.  
  55.  
  56. (* This creates a file of the type specified.  The routine will also create the
  57.    0th record for the new file and initialize it to all zeroes.  This is all
  58.    that needs to be done unless an index record is being created.  To create an
  59.    index file use CreateIndex (located in the BTree unit ) which eventually
  60.    calls this routine.  This routine also creates a BITMAP for the newly
  61.    created file.
  62.  
  63.    note - file name passed as parameter does not contain a file extension.  The
  64.    file will be changed to reflect the proper file extension.                *)
  65.  
  66. procedure CreateFile(var fName : FnString;
  67.                      fType : FileTypes);
  68.  
  69.  
  70. (*\*)
  71. (* This routine will delete a file.  It accomplishes this by removing all
  72.    associated pages from the page buffer, closing the file, deleting the file
  73.    from the files open buffer and deleting the file from disk.  If the file
  74.    type is either DATA or INDEX (determined by file extension) then the
  75.    associated BITMAP file will also be deleted.  Never !!! delete a BITMAP
  76.    file explicitly.                                                          *)
  77.  
  78. procedure DeleteFile(fName : FnString);
  79.  
  80.  
  81. (* This routine will find the first unused record (logical or physical
  82.    depending on the what type of file the record belongs to).  It is used for
  83.    creating a new record.  Once the first unused record is determined it
  84.    will be marked as used (using the BITMAP).  If an INDEX record is called
  85.    for, the routine will find a free physical record.  If a data record is
  86.    being requested, then the routine will locate a free logical record.
  87.    The first free record from the beginning of the file will be returned.
  88.    (BITMAP bit will be set).
  89.  
  90.    This routine is not used to get new records for a BITMAP file since BITMAP
  91.    files don't have BITMAPS.
  92.  
  93.    This is a rather complicated scheme which uses an existence BITMAP to
  94.    determine if each record is in use.  The existence BITMAP file
  95.    consists of some number of physical records.  Each record contains PAGESIZE
  96.    bytes.  Each byte has 8 bits.  Each bit corresponds to 1 record
  97.    in the file.  If the bit is 1 then the record exists, if it is 0 then the
  98.    record does not exist (is not in use).
  99.  
  100.    Remember, a record could be either a logical or a physical record depending
  101.    on file type.                                                             *)
  102.  
  103. function FirstUnUsedRecord(fName : FnString) : RecordNumber;
  104.  
  105.  
  106. (* This routine will delete a Logical or Physical record from a file.  It will
  107.    accomomplish this by setting the appropriate bit in the associated BITMAP
  108.    to zero.  It will also release the page from the buffer if the record is in
  109.    the buffer.  Prior to deleting the record, this routine checks to make sure
  110.    that the record is actually in use.  It it is not then it is not deleted.
  111.    (Nothing happens)
  112.  
  113.    note : This routine will release the page from the buffer if it is an index
  114.    file (a physical record).  If it is a data record the page will not be
  115.    released.  It is somewhat complicated to determine if one or more pages can
  116.    be released in this latter case.  This is because a logical record could use
  117.    part of a physical record or a couple of physical records.  Releasing
  118.    the records will allow that buffer space to be used.  Not releasing it
  119.    means that the page will be swapped out when it gets old.  The routines
  120.    will still function normally.  It is important to note that the logical
  121.    records are always checked against the appropriate bitmat prior to using
  122.    them.  I may do the releasing for logical records latter.                 *)
  123.  
  124.  
  125. procedure DeleteRecord(fName : FnString;
  126.                        rNum : RecordNumber);
  127.  
  128.  
  129. (* This routine will check to see if a logical or physical record is currently
  130.    in use.  It accomplishes this by using the appropriate BITMAP.            *)
  131.  
  132. function RecordUsed(fName : FnString;
  133.                     rNum : RecordNumber) : Boolean;
  134.  
  135.  
  136. (* This routine will set the appropriate BITMAP to show that a given
  137.    logical or physical record is currently in use.                           *)
  138.  
  139. procedure SetRecordUsed(fName : FnString;
  140.                         rNum : RecordNumber);
  141.  
  142.  
  143. (* This routine will determine which logical record or physical record
  144.    (depending on if its a DATA or INDEX file) is the last one used. It uses the
  145.    associated BITMAP to accomplish this.  The routine returns the record number
  146.    of the last record in use.  If no records are in use then 0 will be returned.
  147.    Remember, the INDEX and DATA files do not use record zero for normal data.
  148.    The zero records are used for special purposes if at all.  All files
  149.    (BITMAP,INDEX,DATA) have zeroth records which are created when the file is
  150.    created.                                                                  *)
  151.  
  152. function LastUsedRecord(fName : FnString) : RecordNumber;
  153.  
  154. (*\*)
  155. (*///////////////////// I M P L E M E N T A T I O N /////////////////////////*)
  156.  
  157. implementation
  158.  
  159. const
  160.     FILEEXTENSIONSIZE   = 4;     (* number of characters in a file extension
  161.                                     string                                   *)
  162.  
  163.     BITMAPFILEEXTENSION = '.bit';
  164.     INDEXFILEEXTENSION  = '.idx';
  165.     DATAFILEEXTENSION   = '.dat';
  166.     LLISTFILEEXTENSION  = '.lrf';
  167.  
  168. type
  169.     ExtensionString = String[FILEEXTENSIONSIZE];
  170.  
  171. (* This routine will delete a file extension if it exists.  Otherwise,
  172.    nothing will happen.  The file name will be passed in as a parameter      *)
  173.  
  174. procedure DeleteFileExtension(var fName : FnString);
  175.  
  176. var
  177.     dotPos : 0 .. FNSIZE;
  178.  
  179.     begin
  180.     dotPos := Pos('.',fName);
  181.     if dotPos <> 0 then
  182.         begin
  183.         Delete(fName,dotPos,FILEEXTENSIONSIZE);
  184.         end;
  185.     end;                               (* end of DeleteFileExtension routine *)
  186.  
  187.  
  188. (* This routine will change a file extension to a new file extension.  The
  189.    file name and new extension are passed in as parameters                   *)
  190.  
  191. procedure ChangeFileExtension(var fName : FnString;
  192.                               extension : ExtensionString);
  193.  
  194.     begin
  195.     DeleteFileExtension(fName);
  196.     fName := fName + extension;
  197.     end;                               (* end of ChangeFileExtension routine *)
  198.  
  199. (*\*)
  200. (* This routine will return the type for the given file.  It does this using
  201.    the file extension.                                                       *)
  202.  
  203. function FindFileType(fName : FnString) : FileTypes;
  204.  
  205. var
  206.     dotPos : 1 .. FNSIZE;
  207.     ext : ExtensionString;
  208.  
  209.     begin
  210.     dotPos := Pos('.',fName);
  211.     ext := Copy(fName,dotPos,FILEEXTENSIONSIZE);
  212.     if ext = BITMAPFILEEXTENSION then FindFileType := BITMAP else
  213.     if ext = INDEXFILEEXTENSION  then FindFileType := INDEX else
  214.     if ext = DATAFILEEXTENSION   then FindFileType := DATA else
  215.     if ext = LLISTFILEEXTENSION  then FindFileType := LLIST;
  216.     end;                                      (* end of FindFileType routine *)
  217.  
  218.  
  219. (* This routine will take the input file name and will strip off any extension
  220.    which exists.  It will then add on the file extension which applies to the
  221.    type passed in as a parameter.                                            *)
  222.  
  223. procedure FixFileName(var fName : FnString;
  224.                       fType : FileTypes);
  225.  
  226.     begin
  227.     DeleteFileExtension(fName);
  228.     case fType of
  229.         BITMAP : fName := fName + BITMAPFILEEXTENSION;
  230.         INDEX  : fName := fName + INDEXFILEEXTENSION;
  231.         DATA   : fName := fName + DATAFILEEXTENSION;
  232.         LLIST  : fName := fName + LLISTFILEEXTENSION;
  233.         end;                                        (* end of case statement *)
  234.     end;                                       (* end of FixFileName routine *)
  235.  
  236. (*\*)
  237. (* This creates a file of the type specified.  The routine will also create the
  238.    0th record for the new file and initialize it to all zeroes.  This is all
  239.    that needs to be done unless an index record is being created.  To create an
  240.    index file use CreateIndex (BTree unit) which eventually calls this routine.
  241.    This routine also creates a BITMAP for the newly created file unless the
  242.    file being created is an LLIST file which does not have BITMAPs.
  243.  
  244.    note - file name passed as parameter does not contain a file extension.  The
  245.    file will be changed to reflect the proper file extension.                *)
  246.  
  247. procedure CreateFile(var fName : FnString;
  248.                      fType : FileTypes);
  249.  
  250.     (* This routine creates a BITMAP file for the file fName.                *)
  251.  
  252.     procedure CreateBitMap(fName : FnString);
  253.  
  254.     var
  255.         bmFName : FnString;
  256.  
  257.         begin
  258.         bmFName := fName;
  259.         DeleteFileExtension(bmFName);
  260.         CreateFile(bmFName,BITMAP);
  261.         end;                                  (* end of CreateBitMap routine *)
  262.  
  263. var
  264.     tempFile : File;
  265.     page : SinglePage;
  266.  
  267.     begin
  268.     FixFileName(fName,fType);
  269.     RewriteUntypedFile(fName,tempFile,PAGESIZE);  (* force creation of new file
  270.                                                      - will rewrite old file if
  271.                                                        it exists             *)
  272.     FillChar(page,PAGESIZE,0);                    (* 0th record of all zeros *)
  273.     StorePage(fName,0,page);                             (* store 0th record *)
  274.     if (fType <> BITMAP) and (fType <> LLIST) then
  275.        begin
  276.        CreateBitMap(fName);
  277.        end;
  278.     end;                                        (* end of CreateFile routine *)
  279.  
  280. (*\*)
  281. (* This routine will delete a file.  It accomplishes this by removing all
  282.    associated pages from the page buffer, closing the file, deleting the file
  283.    from the files open buffer and deleting the file from disk.  If the file
  284.    type is either DATA or INDEX (determined by file extension) then the
  285.    associated BITMAP file will also be deleted.  Never !!! delete a BITMAP
  286.    file explicitly.                                                          *)
  287.  
  288. procedure DeleteFile(fName : FnString);
  289.  
  290. var
  291.     bmFName : FnString;
  292.     tempFile : File;
  293.  
  294.     begin
  295.     ReleaseAllPages(fName);                    (* release all pages for this
  296.                                                         file from the buffer *)
  297.     OpenUntypedFile(fName,tempFile,PAGESIZE);        (* needed to get the file
  298.                                                         id (tempFile)        *)
  299.     CloseFile(fName);
  300.     Erase(tempFile);
  301.     case FindFileType (fName) of
  302.         LLIST,
  303.         BITMAP : ;
  304.         INDEX,
  305.         DATA   : begin
  306.                  bmFName := fName;
  307.                  ChangeFileExtension(bmFName,BITMAPFILEEXTENSION);
  308.                  DeleteFile(bmFName);
  309.                  end;
  310.         end;                                        (* end of case statement *)
  311.     end;                                        (* end of DeleteFile routine *)
  312.  
  313. (*\*)
  314. (* This routine will find the first unused record (logical or physical
  315.    depending on the what type of file the record belongs to).  It is used for
  316.    creating a new record.  Once the first unused record is determined it
  317.    will be marked as used (using the BITMAP).  If an INDEX record is called
  318.    for, the routine will find a free physical record.  If a data record is
  319.    being requested, then the routine will locate a free logical record.
  320.    The first free record from the beginning of the file will be returned.
  321.    (BITMAP bit will be set).
  322.  
  323.    This routine is not used to get new records for a BITMAP file since BITMAP
  324.    files don't have BITMAPS.
  325.  
  326.    This is a rather complicated scheme which uses an existence BITMAP to
  327.    determine if each record is in use.  The existence BITMAP file
  328.    consists of some number of physical records.  Each record contains PAGESIZE
  329.    bytes.  Each byte has 8 bits.  Each bit corresponds to 1 record
  330.    in the file.  If the bit is 1 then the record exists, if it is 0 then the
  331.    record does not exist (is not in use).
  332.  
  333.    Remember, a record could be either a logical or a physical record depending
  334.    on file type.                                                             *)
  335.  
  336. function FirstUnUsedRecord(fName : FnString) : RecordNumber;
  337.  
  338. var
  339.     dummyPage,                                  (* used as dummy for calling
  340.                                                    CreatePage when creating new
  341.                                                    page for fname            *)
  342.     page : SinglePage;                          (* copy of page in buffer *)
  343.     byteCtr : 0 .. PAGESIZE;                    (* byte position within page *)
  344.     recCtr : PrNumber;                          (* bit map record counter *)
  345.     found,                                      (* BITMAP record loop *)
  346.     done,                                       (* byte loop *)
  347.     through : boolean;                          (* bit loop *)
  348.     bmFName : FNString;                         (* bit map file name *)
  349.     bitCtr : BytePosition;                      (* bit position within byte  *)
  350.     newRecord : RecordNumber;                   (* new record number *)
  351.  
  352.     begin
  353.     bmFName := fName;
  354.     ChangeFileExtension(bmFName,BITMAPFILEEXTENSION);
  355.     recCtr := 0;
  356.     found := FALSE;
  357.     while not found do                                 (* BITMAP record loop *)
  358.         begin
  359.         if PageExists(bmFName,recCtr) then            (* if past last record *)
  360.             begin
  361.             FetchPage(bmFName,recCtr,page);           (* get next b m record *)
  362.             end
  363.         else
  364.             begin
  365.             FillChar(page,PAGESIZE,0);              (* create new record page
  366.                                                      for this bit map record *)
  367.             end;
  368.         byteCtr := 0;
  369.         done := FALSE;
  370.         while not done do                                       (* byte loop *)
  371.             begin
  372.             if byteCtr = PAGESIZE then
  373.                 begin
  374.                 done := TRUE;
  375.                 end
  376.             else
  377.                 begin
  378.                 byteCtr := byteCtr + 1;
  379.                 bitCtr := 7;
  380.                 through := FALSE;
  381.                 while not through and (page[byteCtr] <> MAXBYTE) do
  382.                               (* the check against MAXBYTE is for efficiency
  383.                                since it will preclude checking individual
  384.                                bits for a byte in which all bits are set
  385.                                to one                                     *)
  386.                     begin                                        (* bit loop *)
  387.                     if not BitOn(page[byteCtr],bitCtr) then
  388.                         begin
  389.                         through := TRUE;
  390.                         done := TRUE;
  391.                         found := TRUE;
  392.                         newRecord := (recCtr * PAGESIZE * 8) +
  393.                                      ((byteCtr - 1) * 8) +
  394.                                      (8 - bitCtr);
  395.                         FirstUnUsedRecord := newRecord;
  396.                         SetBit(page[byteCtr],bitCtr,1);
  397.                         StorePage(bmFName,recCtr,page);
  398.                         end
  399.                     else
  400.                         begin
  401.                         if bitCtr = 0  then
  402.                             begin
  403.                             through := TRUE;
  404.                             end
  405.                         else
  406.                             begin
  407.                             bitCtr := bitCtr - 1;
  408.                             end;
  409.                         end;
  410.                     end;
  411.                 end;
  412.             end;
  413.         recCtr := recCtr + 1;
  414.         end;
  415.     end;                                 (* end of FirstUnUsedRecord routine *)
  416.  
  417. (*\*)
  418. (* This routine will delete a Logical or Physical record from a file.  It will
  419.    accomomplish this by setting the appropriate bit in the associated BITMAP
  420.    to zero.  It will also release the page from the buffer if the record is in
  421.    the buffer.  Prior to deleting the record, this routine checks to make sure
  422.    that the record is actually in use.  It it is not then it is not deleted.
  423.    (Nothing happens)
  424.  
  425.    note : This routine will release the page from the buffer if it is an index
  426.    file (a physical record).  If it is a data record the page will not be
  427.    released.  It is somewhat complicated to determine if one or more pages can
  428.    be released in this latter case.  This is because a logical record could use
  429.    part of a physical record or a couple of physical records.  Releasing
  430.    the records will allow that buffer space to be used.  Not releasing it
  431.    means that the page will be swapped out when it gets old.  The routines
  432.    will still function normally.  It is important to note that the logical
  433.    records are always checked against the appropriate bitmat prior to using
  434.    them.  I may do the releasing for logical records latter.                 *)
  435.  
  436. procedure DeleteRecord(fName : FnString;
  437.                        rNum : RecordNumber);
  438.  
  439. var
  440.     page : SinglePage;
  441.     bitMapRecNum : PrNumber;
  442.     targetBit : BytePosition;
  443.     targetByte : PageRange;
  444.     bmFName : FnString;
  445.     tempString : ExtensionString;
  446.  
  447.     function FileType(fName : FnString) : FileTypes;
  448.     {$V-}                    (* turn off strong type checking for parameters *)
  449.         begin
  450.         EndOfString(fName,4,tempString);
  451.         if tempString = BITMAPFILEEXTENSION then FileType := BITMAP else
  452.         if tempString = INDEXFILEEXTENSION  then FileType := INDEX  else
  453.                                                  FileType := DATA;
  454.         end;                                      (* end of FileType Routine *)
  455.     {$V+}                    (* turn on strong type checking for parameters  *)
  456.  
  457.     begin
  458.     if RecordUsed(fName,rNum) then         (* if it doesn't exist do nothing *)
  459.         begin
  460.         if FileType(fName) = INDEX then              (* see note above *)
  461.             begin
  462.             ReleasePage(fName,rNum);             (* release page from buffer *)
  463.             end;
  464.         bmFName := fName;
  465.         ChangeFileExtension(bmFName,BITMAPFILEEXTENSION);
  466.         bitMapRecNum := (rNum - 1) Div (8 * PAGESIZE); (* correct BITMAP rec *)
  467.         FetchPage(bmFName,bitMapRecNum,page);
  468.         targetByte := (((rNum - 1) mod (8 * PAGESIZE)) div 8) + 1;
  469.         targetbit := (rNum - 1) mod 8;
  470.         targetBit := (targetBit xor 7) and 7; (* this will yield the correct
  471.                                                  bit position within the byte.
  472.                                                  This is necessary because bit
  473.                                                  7 (most significant) in the
  474.                                                  byte is the existence bit for
  475.                                                  the first record not the
  476.                                                  eighth                      *)
  477.  
  478.         SetBit(page[targetByte],targetBit,0);
  479.         StorePage(bmFName,bitMapRecNum,page);
  480.         end;
  481.     end;                                      (* end of DeleteRecord routine *)
  482.  
  483. (*\*)
  484. (* This routine will check to see if a logical or physical record is currently
  485.    in use.  It accomplishes this by using the appropriate BITMAP.            *)
  486.  
  487. function RecordUsed(fName : FnString;
  488.                     rNum : RecordNumber) : Boolean;
  489.  
  490. var
  491.     page : SinglePage;
  492.     bitMapRecNum : PrNumber;
  493.     targetBit : BytePosition;
  494.     targetByte : PageRange;
  495.     bmFName : FnString;
  496.  
  497.     begin
  498.     bmFName := fName;
  499.     ChangeFileExtension(bmFName,BITMAPFILEEXTENSION);
  500.     bitMapRecNum := (rNum - 1) Div (8 * PAGESIZE);   (* correct BITMAP record *)
  501.     if PageExists(bmFName,bitMapRecNum) then
  502.         begin
  503.         FetchPage(bmFName,bitMapRecNum,page);
  504.         targetByte := (((rNum - 1) mod (8 * PAGESIZE)) div 8) + 1;
  505.         targetbit := (rNum - 1) mod 8;
  506.         targetBit := (targetBit xor 7) and 7; (* this will yield the correct
  507.                                                  bit position within the byte.
  508.                                                  This is necessary because bit
  509.                                                  7 (most significant) in the
  510.                                                  byte is the existence bit for
  511.                                                  the first record not the
  512.                                                  eighth                      *)
  513.  
  514.         RecordUsed := BitOn(page[targetByte],targetBit);
  515.         end
  516.     else
  517.         begin
  518.         RecordUsed := FALSE;
  519.         end;
  520.     end;                                        (* end of RecordUsed routine *)
  521.  
  522. (*\*)
  523. (* This routine will set the appropriate BITMAP to show that a given
  524.    logical or physical record is currently in use.                           *)
  525.  
  526. procedure SetRecordUsed(fName : FnString;
  527.                         rNum : RecordNumber);
  528.  
  529. var
  530.     page : SinglePage;
  531.     bitMapRecNum : PrNumber;
  532.     targetBit : BytePosition;
  533.     targetByte : PageRange;
  534.     bmFName : FnString;
  535.  
  536.     begin
  537.     bmFName := fName;
  538.     ChangeFileExtension(bmFName,BITMAPFILEEXTENSION);
  539.     bitMapRecNum := (rNum - 1) Div (8 * PAGESIZE);  (* correct BITMAP record *)
  540.     if PageExists(bmFName,bitMapRecNum) then
  541.         begin
  542.         FetchPage(bmFName,bitMapRecNum,page);
  543.         end
  544.     else
  545.         begin
  546.         FillChar(page,PAGESIZE,0);
  547.         end;
  548.     targetByte := (((rNum - 1) mod (8 * PAGESIZE)) div 8) + 1;
  549.     targetbit := (rNum - 1) mod 8;
  550.     targetBit := (targetBit xor 7) and 7; (* this will yield the correct bit
  551.                                              position within the byte.  This is
  552.                                              necessary because bit 7 (most
  553.                                              significant) in the byte is the
  554.                                              existence bit for the first
  555.                                              record not the eighth           *)
  556.     SetBit(page[targetByte],targetBit,1);        (* set the BITMAP bit to on *)
  557.     StorePage(bmFName,bitMapRecNum,page);
  558.     end;                                        (* end of RecordUsed routine *)
  559.  
  560. (*\*)
  561. (* This routine will determine which logical record or physical record
  562.    (depending on if its a DATA or INDEX file) is the last one used. It uses the
  563.    associated BITMAP to accomplish this.  The routine returns the record number
  564.    of the last record in use.  If no records are in use then 0 will be returned.
  565.    Remember, the INDEX and DATA files do not use record zero for normal data.
  566.    The zero records are used for special purposes if at all.  All files
  567.    (BITMAP,INDEX,DATA) have zeroth records which are created when the file is
  568.    created.                                                                  *)
  569.  
  570. function LastUsedRecord(fName : FnString) : RecordNumber;
  571.  
  572. var
  573.     recCnt : RecordNumber;
  574.     page : SinglePage;
  575.     byteCnt : 0 .. PAGESIZE;
  576.     bitCtr : BytePosition;
  577.     bmFName : FnString;
  578.     found : Boolean;
  579.  
  580.     begin
  581.     bmFName := fName;
  582.     ChangeFileExtension(bmFName,BITMAPFILEEXTENSION);
  583.     recCnt := 0;
  584.     while PageExists(bmFName,recCnt) do
  585.         begin
  586.         recCnt := recCnt + 1;
  587.         end;
  588.     recCnt := recCnt - 1;
  589.     found := FALSE;
  590.     while (not found) and (recCnt >= 0) do
  591.         begin
  592.         FetchPage(bmFName,recCnt,page);
  593.         byteCnt := PAGESIZE;
  594.         while (byteCnt <> 0) and (not found) do
  595.             begin
  596.             if page[byteCnt] <> 0 then
  597.                 begin
  598.                 found := TRUE;
  599.                 end
  600.             else
  601.                 begin
  602.                 byteCnt := byteCnt - 1;
  603.                 end;
  604.             end;
  605.         if not found then
  606.             begin
  607.             recCnt := recCnt - 1;
  608.             end;
  609.         end;
  610.     if found then
  611.         begin
  612.         bitCtr := 0;
  613.         while not BitOn(page[byteCnt],bitCtr) do
  614.             begin
  615.             bitCtr := bitCtr + 1;
  616.             end;
  617.         LastUsedRecord := (((byteCnt - 1) * 8) + ((bitCtr XOR 7) and 7) + 1)
  618.                         + (PAGESIZE * recCnt * 8);
  619.         end
  620.     else
  621.         begin
  622.         LastUsedRecord := 0;                               (* no last record *)
  623.         end;
  624.     end;                                    (* end of LastUsedRecord routine *)
  625.  
  626.  
  627. begin
  628. end.                                                    (* end of Files unit *)
  629.