home *** CD-ROM | disk | FTP | other *** search
/ Power-Programmierung / CD1.mdf / modula2 / library / dbf_file / ndx.mod < prev    next >
Text File  |  1991-07-16  |  80KB  |  1,312 lines

  1. IMPLEMENTATION MODULE NDX;   (* version 1.31 *)
  2.  
  3. (**********************************************************************)
  4. (* Copyright 1988,1989,1990,1991 by David Albert                      *)
  5. (**********************************************************************)
  6. (* This module exports procedures and data to allow Modula-2 users to *)
  7. (* easily access dBase III, III+, and IV index files.  Procedures     *)
  8. (* include: OpenNDX, CloseNDX, Find, Next, Prev, GoTop, GoBottom,     *)
  9. (*          BOF, EOF, FOUND, etc.                                     *)
  10. (* The NDX Module is most effective when used in combination with the *)
  11. (* independent DBF module which provides access to dBase data files.  *)
  12. (* Complete documentation for this module can be found in DBF.DOC     *)
  13. (**********************************************************************)
  14. (* Modification History                                               *)
  15. (*    3/26/91 by DAA removed dependencies on non-standard (non-JPI)   *)
  16. (*                   libraries.  Added error reporting.               *)
  17. (*    3/30/91 by DAA added improved error checking w/AskUser, Halt,...*)
  18. (*    4/09/91 by DAA changed locking scheme to improve efficiency     *)
  19. (*    7/16/91 by DAA added CurKey procedure.                          *)
  20. (**********************************************************************)
  21.  
  22. IMPORT FIO;
  23. FROM IO      IMPORT RdKey, KeyPressed, WrStr, WrCard, WrLn;
  24. FROM Lib     IMPORT Dos, Fill, Move;
  25. FROM Storage IMPORT Available, ALLOCATE, DEALLOCATE;
  26. FROM Str     IMPORT Append, Caps, Compare, Copy, Length, Pos;
  27. FROM SYSTEM  IMPORT Registers;
  28. FROM Window  IMPORT WinDef, WinType, Open, Close, Color, DoubleFrame;
  29.  
  30. CONST
  31.    PageSize    = 512;                           (* index page size = 512    *)
  32.    MaxDepth    = 32;                            (* max depth of b-tree      *)
  33.    MaxRetries  = 50;                            (* max retries on locks     *)
  34.  
  35.    MaxLocks    = 10;
  36.    LockOfs     = 128;
  37.  
  38. TYPE
  39.    NDXFile   = POINTER TO NDXRec;               (* Exported NDX file type   *)
  40.    PagePtr   = POINTER TO PageType;
  41.    KeyPtr    = POINTER TO KeyType;
  42.    StackRec= RECORD                             (* Index stack holds path   *)
  43.       PageNum : LONGCARD;                       (*   from root page to      *)
  44.       KeyNum  : CARDINAL;                       (*   current leaf page/key. *)
  45.    END;                                         (*   Used to traverse the   *)
  46.    StackType = ARRAY[0..MaxDepth] OF StackRec;  (*   b-tree.                *)
  47.    KeyType = RECORD                             (* Index keys contain a ptr *)
  48.       PagePtr : LONGCARD;                       (*   to the next lower page,*)
  49.       RecPtr  : LONGCARD;                       (*   a record pointer, and  *)
  50.       Key     : ARRAY[0..MaxKeyLen-1] OF CHAR;  (*   the key string itself. *)
  51.    END;
  52.    PageType = RECORD                            (* Index pages contain a    *)
  53.       NumKeys : CARDINAL;                       (*   buffer for keys and a  *)
  54.       Dummy   : CARDINAL;                       (*   count of the number of *)
  55.       Keys    : ARRAY[0..PageSize-3 +           (*   keys in the buffer.    *)
  56.                       MaxKeyLen+8] OF CHAR;
  57.    END;
  58.    BufType = RECORD
  59.       PageNum : LONGCARD;
  60.       NextBuf : POINTER TO BufType;
  61.       Page    : POINTER TO PageType;
  62.    END;
  63.    NDXRec = RECORD                              (* Index file variable      *)
  64.       OPEN,                                     (*   File open flag         *)
  65.       RDLOCKED,                                 (*   File read-locked flag  *)
  66.       WRLOCKED,                                 (*   File write-locked flag *)
  67.       CHANGED,                                  (*   File changed flag      *)
  68.       BOF,                                      (*   Top of file flag       *)
  69.       EOF,                                      (*   Bottom of file flag    *)
  70.       FOUND       : BOOLEAN;                    (*   Key found flag         *)
  71.       Name        : ARRAY[0..63] OF CHAR;       (*   file name              *)
  72.       Handle      : CARDINAL;                   (*   file handle            *)
  73.       Shared      : BOOLEAN;                    (*   single/multi-user      *)
  74.       Stack       : StackType;                  (*   Search path            *)
  75.       LockedByte  : CARDINAL;                   (*   Byte read locked       *)
  76.       SPtr        : CARDINAL;                   (*   Search stack pointer   *)
  77.       Page        : PageType;                   (*   Index page buffer      *)
  78.       PNum        : LONGCARD;                   (*   Current index page num *)
  79.       LeafPage    : BOOLEAN;                    (*   Cur Page = Leaf page?  *)
  80.       Key         : KeyType;                    (*   Current index key      *)
  81.       KPtr        : KeyPtr;                     (*   Ptr to Key in Idx page *)
  82.       KNum        : CARDINAL;                   (*   Num of key in Idx page *)
  83.       NumBufs     : CARDINAL;                   (*   Number of buffers      *)
  84.       PageBuf     : BufType;                    (*   Page Buffer list       *)
  85.       (* IDX Header *)                          (* Index file header        *)
  86.       Root        : LONGCARD;                   (*   Root page number       *)
  87.       NextFree    : LONGCARD;                   (*   Next free page number  *)
  88.       Dummy1      : LONGCARD;                   (*   Unused                 *)
  89.       KeyLen      : CARDINAL;                   (*   Length of key field    *)
  90.       KeysPerPage : CARDINAL;                   (*   # Keys per index page  *)
  91.       Numeric     : CARDINAL;                   (*   1 = Numeric or Date    *)
  92.       KeySize     : CARDINAL;                   (*   Key + Pointers size    *)
  93.       Dummy2      : CARDINAL;                   (*   Unused                 *)
  94.       Unique      : CARDINAL;                   (*   1 = Unique             *)
  95.       KeyField    : ARRAY[0..99] OF CHAR;       (*   Key field expression   *)
  96.       Changes     : LONGCARD;                   (*   bumped when ndx changd *)
  97.       Dummy3      : ARRAY[0..383] OF CHAR;      (*   Unused                 *)
  98.    END; (* NDXRec *)
  99.  
  100. (****************************************************************************)
  101. (* Miscellaneous low-level procedures                                       *)
  102. (****************************************************************************)
  103.  
  104. PROCEDURE PadRight(VAR S : ARRAY OF CHAR; Len : CARDINAL);
  105. VAR N : CARDINAL;
  106. BEGIN
  107.    N := Length(S);                              (* Pad from end of string   *)
  108.    WHILE N < Len DO                             (*    out to Len bytes with *)
  109.       S[N] := ' ';                              (*    spaces.  This is used *)
  110.       INC(N);                                   (*    to convert modula-2   *)
  111.    END;                                         (*    strings to dBase fmt. *)
  112.    S[N] := 0C;                                  (* terminate string w/null  *)
  113. END PadRight;
  114.  
  115. (****************************************************************************)
  116. (* Dialog boxes                                                             *)
  117. (****************************************************************************)
  118.  
  119. PROCEDURE ReplaceDiskDialog;
  120. VAR DialogWin : WinType;
  121.     Key       : CHAR;
  122. BEGIN
  123.    DialogWin := Open(WinDef(20, 5, 60, 10, White, Black,
  124.       TRUE, TRUE, FALSE, TRUE, DoubleFrame, White, Black));
  125.    WrLn;
  126.    WrStr('Replace data disk in drive.'); WrLn;
  127.    WrStr('Press any key to continue or Esc to quit...');
  128.    Key := RdKey();
  129.    Close(DialogWin);
  130.    IF (Key = 33C) THEN HALT; END;
  131. END ReplaceDiskDialog;
  132.  
  133. PROCEDURE LockedDialog() : CHAR;
  134. VAR DialogWin : WinType;
  135.     Key       : CHAR;
  136. BEGIN
  137.    WHILE KeyPressed() DO                        (* Clear any buffered       *)
  138.       Key := RdKey();                           (*   keystrokes             *)
  139.    END;
  140.    DialogWin := Open(WinDef(20, 5, 60, 10,      (* Open dialog window       *)
  141.       White, Black, TRUE, TRUE, FALSE, TRUE,
  142.       DoubleFrame, White, Black));
  143.    WrLn;                                        (* Display message          *)
  144.    WrStr('File is locked by another user.');    (*    indicating that file  *)
  145.    WrLn;                                        (*    is locked by another  *)
  146.    WrStr("Press 'W' to wait or 'A' to abort "); (*    user and ask if       *)
  147.    REPEAT                                       (*    user wishes to wait   *)
  148.       Key := CAP(RdKey());                      (*    or abort program.     *)
  149.    UNTIL (Key = 'W')OR(Key = 'A')OR(Key = 33C); (* When user responds,      *)
  150.    Close(DialogWin);                            (*    close dialog window   *)
  151.    RETURN Key;                                  (*    and return choice.    *)
  152. END LockedDialog;
  153.  
  154. (****************************************************************************)
  155. (* DOS File I/O                                                             *)
  156. (****************************************************************************)
  157.  
  158. PROCEDURE FLock(F : FIO.File; Ofs,Len : LONGCARD) : CARDINAL;
  159. CONST CF = 0;                                   (* Lock an area in a file   *)
  160. TYPE  AdrType = RECORD                          (*   via DOS record locking *)
  161.         Offset, Segment : CARDINAL;             (*   calls.                 *)
  162.       END;
  163. VAR   Regs   : Registers;
  164.       AdrPtr : AdrType;
  165. BEGIN
  166.    Regs.AX := 5C00H;                            (* DOS function 5Ch        *)
  167.    Regs.BX := F;                                (*     subfunction 00      *)
  168.    AdrPtr  := AdrType(Ofs);                     (*     locks range of file *)
  169.    Regs.CX := AdrPtr.Segment;                   (*     and returns with CF *)
  170.    Regs.DX := AdrPtr.Offset;                    (*     set if range already*)
  171.    AdrPtr  := AdrType(Len);                     (*     locked.             *)
  172.    Regs.SI := AdrPtr.Segment;                   (*     If CF not set, then *)
  173.    Regs.DI := AdrPtr.Offset;                    (*     area is locked OK.  *)
  174.    Dos(Regs);
  175.    IF CF IN Regs.Flags
  176.       THEN RETURN Regs.AX;
  177.       ELSE RETURN 0;
  178.    END;
  179. END FLock;
  180.  
  181. PROCEDURE FUnLock(F : FIO.File; Ofs,Len : LONGCARD) : CARDINAL;
  182. CONST CF = 0;                                   (* Unlock area in a file   *)
  183. TYPE  AdrType = RECORD                          (*   via DOS record unlock *)
  184.         Offset, Segment : CARDINAL;             (*   call.                 *)
  185.       END;
  186. VAR   Regs   : Registers;
  187.       AdrPtr : AdrType;
  188. BEGIN
  189.    Regs.AX := 5C01H;                            (* DOS function 5Ch        *)
  190.    Regs.BX := F;                                (*     subfunction 01h     *)
  191.    AdrPtr  := AdrType(Ofs);                     (*     unlocks range in a  *)
  192.    Regs.CX := AdrPtr.Segment;                   (*     file that was locked*)
  193.    Regs.DX := AdrPtr.Offset;                    (*     with subfunction 00 *)
  194.    AdrPtr  := AdrType(Len);
  195.    Regs.SI := AdrPtr.Segment;
  196.    Regs.DI := AdrPtr.Offset;
  197.    Dos(Regs);
  198.    IF CF IN Regs.Flags
  199.       THEN RETURN Regs.AX;
  200.       ELSE RETURN 0;
  201.    END;
  202. END FUnLock;
  203.  
  204. PROCEDURE FlushBuffers(F : FIO.File);           (* Flush any buffers for    *)
  205. CONST CF = 0;                                   (*   file specified to disk *)
  206. VAR Regs    : Registers;                        (* ( used to assure writes  *)
  207. BEGIN                                           (*   make it to disk. )     *)
  208.    REPEAT
  209.       Regs.AH := 68H;
  210.       Regs.BX := F;
  211.       Dos(Regs);
  212.       IF (CF IN Regs.Flags) AND (Regs.AX = 34) THEN
  213.          ReplaceDiskDialog();
  214.       END;
  215.    UNTIL NOT ((CF IN Regs.Flags) AND (Regs.AX = 34));
  216. END FlushBuffers;
  217.  
  218. PROCEDURE GetExtErr() : CARDINAL;               (* Get extended DOS error   *)
  219. VAR Regs : Registers;                           (*    information.          *)
  220. BEGIN
  221.    Regs.AH := 59H;
  222.    Dos(Regs);
  223.    RETURN Regs.AX;
  224. END GetExtErr;
  225.  
  226. PROCEDURE SetRetries(Delay,Retries : CARDINAL); (* Set # of retries, and    *)
  227. VAR Regs : Registers;                           (*    delay between each    *)
  228. BEGIN                                           (*    retry when attempting *)
  229.    Regs.AX := 440BH;                            (*    to lock a region in a *)
  230.    Regs.CX := Delay;                            (*    file.                 *)
  231.    Regs.DX := Retries;
  232.    Dos(Regs);
  233. END SetRetries;
  234.  
  235. (****************************************************************************)
  236. (* Error handling routines                                                  *)
  237. (****************************************************************************)
  238.  
  239. PROCEDURE UnLockNDX(N : NDXFile);
  240. VAR Status, L : CARDINAL;
  241. BEGIN
  242.    IF N^.WRLOCKED THEN
  243.       IF N^.LockedByte = 0 THEN
  244.          Status := FUnLock(N^.Handle, LockOfs, MaxLocks+1);
  245.       ELSE
  246.          FOR L := 0 TO MaxLocks DO
  247.            Status := FUnLock(N^.Handle, VAL(LONGCARD, LockOfs+L), 1);
  248.          END;
  249.       END;
  250.       N^.WRLOCKED := FALSE;
  251.    END;
  252.    IF N^.RDLOCKED THEN
  253.       Status:=FUnLock(N^.Handle,VAL(LONGCARD,(LockOfs+N^.LockedByte)),1);
  254.       N^.RDLOCKED := FALSE;
  255.    END;
  256. END UnLockNDX;
  257.  
  258. PROCEDURE HandleError(Proc : ARRAY OF CHAR; N : NDXFile; Code : CARDINAL);
  259. VAR DialogWin : WinType;
  260.     Key       : CHAR;
  261. BEGIN
  262.    ErrCode := Code;
  263.    DosCode := GetExtErr();
  264.    IF (N # NIL) THEN UnLockNDX(N); END;         (* Remove any current locks *)
  265.    IF ErrCheck = Halt THEN                      (* If terminating program,  *)
  266.       IF N # NIL THEN FIO.Close(N^.Handle); END;(*    close index           *)
  267.    END;
  268.    IF ErrCheck = None THEN                      (* If no Internal err chking*)
  269.       RETURN;                                   (*    just return error code*)
  270.    END;                                         (*    for caller to handle  *)
  271.    DialogWin := Open(WinDef(20, 5, 60, 10,      (* Open dialog window       *)
  272.       White, Black, TRUE, TRUE, FALSE, TRUE,
  273.       DoubleFrame, White, Black));
  274.    WrStr('Error:'); WrLn;
  275.    WrStr('  Procedure : '); WrStr(Proc); WrLn;
  276.    IF (N # NIL) THEN
  277.       WrStr('  Index file: '); WrStr(N^.Name); WrLn;
  278.    END;
  279.    WrStr('  Message   : ');
  280.    CASE Code OF
  281.       ErrOpen   : WrStr('Unable to find/open file.');
  282.    |  ErrClose  : WrStr('Unable to close file.');
  283.    |  ErrRead   : WrStr('Unable to read page.');
  284.    |  ErrWrite  : WrStr('Unable to write page.');
  285.    |  ErrSeek   : WrStr('Unable to seek to page.');
  286.    |  ErrLock   : WrStr('Index/page locked by another user.');
  287.    |  ErrUnLock : WrStr('Unable to unlock index/page.');
  288.    |  ErrHandle : WrStr('Index file not open.');
  289.    |  ErrMemory : WrStr('Insufficient memory.');
  290.    |  ErrPageNo : WrStr('Invalid Page Number.');
  291.    |  ErrBadNDX : WrStr('Index file invalid or damaged.');
  292.    |  ErrLockedNDX : WrStr('Index file locked by another user.');
  293.    ELSE WrStr('error cause unknown.');
  294.    END;
  295.    WrLn;
  296.    IF Code < ErrPageNo THEN
  297.       WrStr('  DOS Code  : '); WrCard(DosCode, 3); WrLn;
  298.    END;
  299.    WrLn;
  300.    IF ErrCheck = AskUser THEN
  301.       WrStr('Press any key to continue or Esc to abort. ');
  302.       Key := RdKey();
  303.       Close(DialogWin);
  304.       IF Key = 33C THEN HALT; END;
  305.    ELSIF ErrCheck = Halt THEN
  306.       WrStr('Press any key to quit. ');
  307.       Key := RdKey();
  308.       Close(DialogWin);
  309.       HALT;
  310.    END;
  311. END HandleError;
  312.  
  313. (****************************************************************************)
  314. (* Concurrency controls - ReadLock, ReadUnLock, WriteLock, WriteUnLock,     *)
  315. (*                        ReadHeader, WriteHeader, UpdateHeader             *)
  316. (****************************************************************************)
  317. (* LOCKING SCHEME                                                           *)
  318. (*  In order to work with the b-trees in a multi-user environment, some     *)
  319. (*  concurrency control is required to prevent damage to the index and to   *)
  320. (*  maintain integrity during index accesses.                               *)
  321. (*  The scheme used here allows for multiple simultaneous readers, but      *)
  322. (*  only one writer at a time.  When writing to the index, no readers, and  *)
  323. (*  no other writers may access the index until the update is complete.     *)
  324. (*  - After each update, a 32-bit counter in an unused area of the index    *)
  325. (*    header is incremented to notify other users of the change.            *)
  326. (*  - In order to support all networks/multi-user DOS operating systems,    *)
  327. (*    only standard DOS record locking function (5Ch) is used for control.  *)
  328. (* READER PROTOCOL:                                                         *)
  329. (*  10 otherwise unused bytes in the index header are used as 'read locks'. *)
  330. (*  When a reader wishes to access the file, he locks the first available   *)
  331. (*  read lock byte.  When done, he unlocks it.   The number of simultaneous *)
  332. (*  readers is limited by the number of read lock bytes.                    *)
  333. (* WRITER PROTOCOL:                                                         *)
  334. (*  1 additional unused byte in the index header is the 'write lock'.  When *)
  335. (*  a writer wishes to update the file, he must lock this byte and all of   *)
  336. (*  the read lock bytes as well.  The writer first attempts to lock both    *)
  337. (*  the write lock and all of the read locks at the same time.  If unable   *)
  338. (*  to do this, other readers or writers are present.  So the writer must   *)
  339. (*  first lock the write lock byte, insuring no other writers are present.  *)
  340. (*  Then, each read lock is locked as it becomes available, gradually       *)
  341. (*  squeezing out any readers.  Eventually, the writer will get control.    *)
  342. (*  When the update is complete, all locks are released.                    *)
  343.  
  344. PROCEDURE ReadLock(I : NDXFile);                (* Lock one of the read     *)
  345. VAR Status : CARDINAL;                          (*   lock bytes or wait     *)
  346.     N      : CARDINAL;                          (*   until one is available *)
  347.     FPtr   : LONGCARD;                          (*   This locks out writers *)
  348. BEGIN
  349.    IF NOT I^.Shared THEN RETURN; END;           (* If not sharing, done.    *)
  350.    IF ErrCode # 0 THEN RETURN; END;             (* If error, abort now.     *)
  351.    IF I^.RDLOCKED THEN RETURN; END;             (* If already locked, done. *)
  352.    LOOP                                         (* Else enter lock loop.    *)
  353.       FOR N := 1 TO MaxLocks DO                 (* For each read lock byte, *)
  354.          FPtr := VAL(LONGCARD, (LockOfs+N));    (*    Set pointer to lock   *)
  355.          Status := FLock(I^.Handle, FPtr, 1);   (*    Attempt to grab lock  *)
  356.          IF Status <= 1 THEN                    (*    If successful,        *)
  357.             I^.RDLOCKED := TRUE;                (*       set locked flag,   *)
  358.             I^.LockedByte := LockOfs+N;         (*       save locked byte # *)
  359.             RETURN;                             (*       and return now.    *)
  360.          END; (* IF locked *)                   (*    Else, try next lock   *)
  361.       END; (* FOR each lock byte *)             (*    until all tried       *)
  362.       IF KeyPressed() THEN                      (* If no locks free, and key*)
  363.          IF LockedDialog() = 'A' THEN           (*    pressed, ask user if  *)
  364.             HandleError('ReadLock', I, ErrLock);(*    they wish to abort.   *)
  365.             RETURN;                             (*    If so disp msg & abort*)
  366.          END; (* IF user aborts *)              (* Else, while if no key    *)
  367.       END; (* IF Keypressed *)                  (*    pressed, keep trying  *)
  368.    END; (* LOOP *)                              (* Continue till locked.    *)
  369. END ReadLock;
  370.  
  371. PROCEDURE ReadUnLock(I : NDXFile);              (* Release read lock byte   *)
  372. VAR Status : CARDINAL;                          (*    to allow writers in.  *)
  373.     FPtr   : LONGCARD;
  374. BEGIN
  375.   IF NOT I^.Shared THEN RETURN; END;            (* If not sharing, done.    *)
  376.   IF ErrCode # 0 THEN RETURN; END;              (* If error, abort now.     *)
  377.   IF NOT I^.RDLOCKED THEN RETURN; END;          (* If not locked, then done *)
  378.   FPtr := VAL(LONGCARD, I^.LockedByte);         (* Else set pointer to byte *)
  379.   Status := FUnLock(I^.Handle, FPtr, 1);        (*    locked and unlock it. *)
  380.   I^.RDLOCKED := FALSE;                         (* Set locked status FALSE, *)
  381.   IF Status > 1 THEN                            (* If error unlocking,      *)
  382.      HandleError('ReadUnLock', I, ErrUnLock);   (*    set error code.       *)
  383.   END;                                          (* Return.                  *)
  384. END ReadUnLock;
  385.  
  386. PROCEDURE WriteLock(I : NDXFile);               (* Lock the write lock byte *)
  387. VAR Status : CARDINAL;                          (*    to lock out other     *)
  388.     N      : CARDINAL;                          (*    writers, then all of  *)
  389.     FPtr   : LONGCARD;                          (*    the read lock bytes to*)
  390.     NumLocked : CARDINAL;                       (*    lock out readers.     *)
  391.     Locked : ARRAY [1..MaxLocks] OF BOOLEAN;
  392. BEGIN
  393.    IF NOT I^.Shared THEN RETURN; END;           (* If not sharing, done.    *)
  394.    IF ErrCode # 0 THEN RETURN; END;             (* If error, abort now.     *)
  395.    IF I^.WRLOCKED THEN RETURN; END;             (* If already locked, done. *)
  396.    Status:=FLock(I^.Handle,LockOfs,MaxLocks+1); (* Attempt to lock all locks*)
  397.    IF Status <= 1 THEN                          (* at once.  If successful, *)
  398.       I^.WRLOCKED := TRUE;                      (*    set locked flag=TRUE  *)
  399.       I^.LockedByte := 0;                       (*    set lock type=BLOCK   *)
  400.       RETURN;                                   (*    and return.           *)
  401.    END;                                         (* Else other users present *)
  402.    REPEAT                                       (* Grab write lock byte to  *)
  403.       Status := FLock(I^.Handle, LockOfs, 1);   (*    lock out other writers*)
  404.       IF (Status > 1) AND (KeyPressed()) THEN   (* If unable, and key is hit*)
  405.          IF LockedDialog() = 'A' THEN           (*    If choses to abort,   *)
  406.             HandleError('WriteLock',I,ErrLock); (*       display error msg  *)
  407.             RETURN;                             (*       and abort now.     *)
  408.          END;                                   (*    Else, user will wait  *)
  409.       END;                                      (* Else NDX is write locked *)
  410.    UNTIL Status <= 1;                           (* Continue till write lockd*)
  411.    I^.WRLOCKED   := TRUE;                       (* set write locked flag    *)
  412.    I^.LockedByte := 1;                          (* set lock type=INDIVIDUAL *)
  413.    FOR N := 1 TO MaxLocks DO                    (* Initialize an array      *)
  414.       Locked[N] := FALSE;                       (*    of flags for each     *)
  415.    END;                                         (*    read lock             *)
  416.    NumLocked := 0;                              (* Set # locked so far to 0 *)
  417.    REPEAT                                       (* Lock bytes loop          *)
  418.       FOR N := 1 TO MaxLocks DO                 (*  For each byte to lock,  *)
  419.          IF NOT Locked[N] THEN                  (*   If not already locked, *)
  420.             FPtr := VAL(LONGCARD, (LockOfs+N)); (*      calc byte position, *)
  421.             Status := FLock(I^.Handle, FPtr, 1);(*      and attempt to lock *)
  422.             IF Status <= 1 THEN                 (*      If successful,      *)
  423.                Locked[N] := TRUE;               (*         set locked flag, *)
  424.                INC(NumLocked);                  (*         and bump count.  *)
  425.             END;                                (*   Else unable to lock it *)
  426.          END;                                   (*  Else byte already locked*)
  427.       END;                                      (*  Continue for each byte  *)
  428.       IF KeyPressed() THEN                      (*  After each pass, if key *)
  429.          IF LockedDialog() = 'A' THEN           (*  hit, ask: Wait/Abort    *)
  430.             HandleError('WriteLock',I,ErrLock); (*  If user aborts,         *)
  431.             RETURN;                             (*     display error message*)
  432.          END;                                   (*     and abort procedure. *)
  433.       END;                                      (*  Continue grabbing locks *)
  434.    UNTIL (NumLocked = MaxLocks);                (*     until all are locked *)
  435. END WriteLock;
  436.  
  437. PROCEDURE WriteUnLock(I : NDXFile);             (* Release write lock byte  *)
  438. VAR N      : CARDINAL;                          (*    and all read lock     *)
  439.     Status : CARDINAL;                          (*    bytes in the same way *)
  440.     FPtr   : LONGCARD;                          (*    they were locked.     *)
  441. BEGIN
  442.    IF NOT I^.Shared THEN RETURN; END;           (* If not sharing, done.    *)
  443.    IF ErrCode # 0 THEN RETURN; END;             (* If error, abort now.     *)
  444.    IF NOT I^.WRLOCKED THEN RETURN; END;         (* If not locked, done.     *)
  445.    IF I^.LockedByte = 0 THEN                    (* If block write locked    *)
  446.     Status:=FUnLock(I^.Handle,LockOfs,MaxLocks+1); (* release block         *)
  447.     I^.WRLOCKED := FALSE;                          (* set file unlocked flag*)
  448.     RETURN;                                        (* and return to caller  *)
  449.    END;                                         (* Else individual locks so *)
  450.    FOR N := 0 TO MaxLocks DO                    (* Release each lock byte   *)
  451.       FPtr := VAL(LONGCARD, (LockOfs+N));       (*    individually          *)
  452.       Status := FUnLock(I^.Handle, FPtr, 1);    (*    as they were created  *)
  453.    END;                                         (* Until all bytes unlocked *)
  454.    I^.WRLOCKED := FALSE;                        (* set file unlocked flag   *)
  455. END WriteUnLock;
  456.  
  457. PROCEDURE ReadHeader(I : NDXFile);
  458. VAR TempIOcheck : BOOLEAN;
  459.     LastChanges : LONGCARD;
  460.     nRead       : CARDINAL;
  461. BEGIN
  462.    IF ErrCode # 0 THEN RETURN; END;             (* If error, abort now.     *)
  463.    LastChanges := I^.Changes;                   (* Save last changes count  *)
  464.    TempIOcheck := FIO.IOcheck;                  (* Save cur IOcheck status  *)
  465.    FIO.IOcheck := FALSE;                        (* Turn off FIO err checking*)
  466.    FIO.Seek(I^.Handle, 0);                      (* Seek to index header     *)
  467.    IF FIO.IOresult() > 0 THEN                   (* If error seeking,        *)
  468.       FIO.IOcheck := TempIOcheck;               (*   restore IOcheck status *)
  469.       HandleError('ReadHeader', I, ErrSeek);    (*   handle error           *)
  470.       RETURN;                                   (*   and abort procedure.   *)
  471.    END;                                         (* Else, with file ptr set, *)
  472.    nRead := FIO.RdBin(I^.Handle, I^.Root, 128); (* Get header from NDX file *)
  473.    FIO.IOcheck := TempIOcheck;                  (* Restore IOcheck status   *)
  474.    IF (nRead#128) OR (FIO.IOresult() > 0) THEN  (* If error reading header, *)
  475.       IF (GetExtErr() = 33) THEN                (* If page was locked,      *)
  476.          HandleError('ReadHeader', I, ErrLock); (*    handle lock error.    *)
  477.       ELSE                                      (* Else was read error, so  *)
  478.          HandleError('ReadHeader', I, ErrRead); (*    handle read error     *)
  479.       END;                                      (* With either type of err, *)
  480.       I^.CHANGED := TRUE;                       (*    set header changed,   *)
  481.       RETURN;                                   (*    and abort procedure.  *)
  482.    END;                                         (* Else, header read OK.    *)
  483.    I^.CHANGED := I^.Changes # LastChanges;      (* Set index changed status *)
  484. END ReadHeader;
  485.  
  486. PROCEDURE UpdateHeader(I : NDXFile);
  487. BEGIN
  488.    IF NOT I^.Shared THEN RETURN; END;           (* If not sharing, done.    *)
  489.    IF ErrCode # 0 THEN RETURN; END;             (* If error, abort now.     *)
  490.    ReadHeader(I);                               (* Else check for changes   *)
  491. END UpdateHeader;
  492.  
  493. PROCEDURE WriteHeader(I : NDXFile);
  494. VAR TempIOcheck : BOOLEAN;
  495. BEGIN
  496.    IF ErrCode # 0 THEN RETURN; END;             (* If error, abort now.     *)
  497.    TempIOcheck := FIO.IOcheck;                  (* Save cur IOcheck status  *)
  498.    FIO.IOcheck := FALSE;                        (* Turn off FIO err checking*)
  499.    FIO.Seek(I^.Handle, 0);                      (* Seek to start of header  *)
  500.    IF FIO.IOresult() > 0 THEN                   (* If error seeking,        *)
  501.       FIO.IOcheck := TempIOcheck;               (*   restore IOcheck status *)
  502.       HandleError('WriteHeader', I, ErrSeek);   (*   handle error           *)
  503.       RETURN;                                   (*   and abort procedure.   *)
  504.    END;                                         (* Else, with file ptr set, *)
  505.    FIO.WrBin(I^.Handle, I^.Root, PageSize);     (* Write header to disk     *)
  506.    FIO.IOcheck := TempIOcheck;                  (* Restore IOcheck status   *)
  507.    IF (FIO.IOresult() > 0) THEN                 (* If error writing page    *)
  508.       HandleError('WriteHeader', I, ErrWrite);  (*    handle error          *)
  509.       RETURN;                                   (*    and abort procedure   *)
  510.    END;
  511.    IF Safety THEN                               (* If Safety mode active,   *)
  512.       FlushBuffers(I^.Handle);                  (*    Flush changes to disk *)
  513.    END;                                         (*    to insure all saved.  *)
  514. END WriteHeader;
  515.  
  516. (****************************************************************************)
  517. (* Page oriented procedures - InitPage, GetPage, PutPage                    *)
  518. (****************************************************************************)
  519.  
  520. PROCEDURE InitPage(VAR P : PageType);
  521. BEGIN
  522.   IF ErrCode # 0 THEN RETURN; END;              (* If error, abort now.     *)
  523.   P.NumKeys := 0;                               (* Init keys in page to 0   *)
  524.   P.Dummy   := 0;                               (* Init dummy field         *)
  525.   Fill(ADR(P.Keys), SIZE(P.Keys), 0C);          (* Init key buffer          *)
  526. END InitPage;
  527.  
  528. PROCEDURE GetPage (I : NDXFile);
  529. VAR FPtr        : LONGCARD;
  530.     nRead       : CARDINAL;
  531.     TempIOcheck : BOOLEAN;
  532. BEGIN
  533.    IF ErrCode # 0 THEN RETURN; END;             (* If error, abort now.     *)
  534.    IF (I^.PNum=0)OR(I^.PNum>=I^.NextFree) THEN  (* If invalid page number   *)
  535.       HandleError('GetPage', I, ErrPageNo);     (*    handle error          *)
  536.       RETURN;                                   (*    and abort procedure.  *)
  537.    END;                                         (* Else, valid page to get  *)
  538.    TempIOcheck := FIO.IOcheck;                  (* Save cur IOcheck status  *)
  539.    FIO.IOcheck := FALSE;                        (* Turn off FIO err checking*)
  540.    FPtr := PageSize * I^.PNum;                  (* Calculate offset of page *)
  541.    FIO.Seek(I^.Handle, FPtr);                   (* in file and seek to it.  *)
  542.    IF FIO.IOresult() > 0 THEN                   (* If error seeking,        *)
  543.       HandleError('GetPage', I, ErrSeek);       (*   handle error           *)
  544.       FIO.IOcheck := TempIOcheck;               (*   restore IOcheck status *)
  545.       RETURN;                                   (*   and abort procedure.   *)
  546.    END;                                         (* Else, with file ptr set, *)
  547.    nRead:=FIO.RdBin(I^.Handle,I^.Page,PageSize);(* Read page from disk.     *)
  548.    FIO.IOcheck := TempIOcheck;                  (* Restore IOcheck status   *)
  549.    IF (nRead#PageSize)OR(FIO.IOresult()>0) THEN (* If error reading page,   *)
  550.       IF GetExtErr()=33 THEN                    (*    If page was locked,   *)
  551.          HandleError('GetPage', I, ErrLock);    (*       handle lock error  *)
  552.       ELSE                                      (*    Else, read error, so  *)
  553.          HandleError('GetPage', I, ErrRead);    (*       handle read error  *)
  554.       END;                                      (*    Either way,           *)
  555.       RETURN;                                   (*    abort procedure       *)
  556.    END;                                         (* Else, page read OK.      *)
  557.    I^.KPtr := ADR(I^.Page.Keys[0]);             (* Set pointer to first key *)
  558.    I^.LeafPage := (I^.KPtr^.PagePtr = 0);       (* Set leaf page flag       *)
  559. END GetPage;
  560.  
  561. PROCEDURE PutPage (I : NDXFile);
  562. VAR FPtr        : LONGCARD;
  563.     TempIOcheck : BOOLEAN;
  564. BEGIN
  565.    IF ErrCode # 0 THEN RETURN; END;             (* If error, abort now.     *)
  566.    IF (I^.PNum=0) OR (I^.PNum>I^.NextFree) THEN (* If invalid page number   *)
  567.       HandleError('PutPage', I, ErrPageNo);     (*    handle error          *)
  568.       RETURN;                                   (*    and abort procedure.  *)
  569.    END;                                         (* Else valid page to write *)
  570.    TempIOcheck := FIO.IOcheck;                  (* Save cur IOcheck status  *)
  571.    FIO.IOcheck := FALSE;                        (* Turn off FIO err checking*)
  572.    FPtr := PageSize * I^.PNum;                  (* Calculate offset of page *)
  573.    FIO.Seek(I^.Handle, FPtr);                   (* in file and seek to it.  *)
  574.    IF FIO.IOresult() > 0 THEN                   (* If error seeking,        *)
  575.       HandleError('PutPage', I, ErrSeek);       (*    handle error          *)
  576.       FIO.IOcheck := TempIOcheck;               (*    restore IOcheck state *)
  577.       RETURN;                                   (*    and abort procedure   *)
  578.    END;                                         (* Else with file ptr set   *)
  579.    FIO.WrBin(I^.Handle, I^.Page, PageSize);     (*    write page to disk.   *)
  580.    FIO.IOcheck := TempIOcheck;                  (* Restore IOcheck status   *)
  581.    IF FIO.IOresult() > 0 THEN                   (* If error writing page,   *)
  582.       HandleError('PutPage', I, ErrWrite);      (*    handle error          *)
  583.       RETURN;                                   (*    and abort procedure   *)
  584.    END;                                         (* Else page written OK.    *)
  585.    IF Safety THEN                               (* If safety mode active,   *)
  586.       FlushBuffers(I^.Handle);                  (*    flush writes from     *)
  587.    END;                                         (*    cache to disk now.    *)
  588. END PutPage;
  589.  
  590. (****************************************************************************)
  591. (* Stack oriented procedures - ClearStack, PushPage, PopPage                *)
  592. (****************************************************************************)
  593.  
  594. PROCEDURE ClearStack(I : NDXFile);
  595. BEGIN
  596.    I^.SPtr := 0;                                (* Reset stack pointer      *)
  597. END ClearStack;
  598.  
  599. PROCEDURE PushPage(I : NDXFile);
  600. BEGIN
  601.    IF ErrCode # 0 THEN RETURN; END;             (* If error, abort now.     *)
  602.    IF I^.SPtr >= MaxDepth THEN                  (* If past max stack depth, *)
  603.       HandleError('PushPage', I, ErrBadNDX);    (*    handle error          *)
  604.       RETURN;                                   (*    and abort procedure   *)
  605.    END;
  606.    I^.Stack[I^.SPtr].PageNum := I^.PNum;        (* Save current page number *)
  607.    I^.Stack[I^.SPtr].KeyNum  := I^.KNum;        (* Save current key number  *)
  608.    INC(I^.SPtr);                                (* Bump stack pointer       *)
  609. END PushPage;
  610.  
  611. PROCEDURE PopPage(I : NDXFile);
  612. BEGIN
  613.    IF ErrCode # 0 THEN RETURN; END;             (* If error, abort now.     *)
  614.    IF I^.SPtr = 0 THEN                          (* If stack is empty,       *)
  615.       HandleError('PopPage', I, ErrBadNDX);     (*    handle error          *)
  616.       RETURN;                                   (*    and abort procedure   *)
  617.    END;                                         (* Else page is on stack    *)
  618.    DEC(I^.SPtr);                                (* Pop stack                *)
  619.    I^.PNum := I^.Stack[I^.SPtr].PageNum;        (* Get page number and      *)
  620.    I^.KNum := I^.Stack[I^.SPtr].KeyNum;         (* key number from stack    *)
  621. END PopPage;
  622.  
  623. PROCEDURE GetPtr(I : NDXFile) : LONGCARD;
  624. VAR KeyOfs : CARDINAL;
  625. BEGIN
  626.    IF ErrCode # 0 THEN RETURN 0; END;           (* If error, abort now.     *)
  627.    WITH I^ DO
  628.       KeyOfs := (KNum - 1) * KeySize;           (* Calc location of key     *)
  629.       KPtr   := ADR(Page.Keys[KeyOfs]);         (* Set ptr to key in page   *)
  630.       Key.RecPtr := KPtr^.RecPtr;               (* Copy out record pointer  *)
  631.       Key.PagePtr := KPtr^.PagePtr;             (* Copy out page pointer    *)
  632.       Move(ADR(KPtr^.Key),ADR(Key.Key),KeyLen); (* Copy out key str.        *)
  633.       IF LeafPage                               (* If page is leaf page,    *)
  634.          THEN RETURN Key.RecPtr;                (*    return record pointer *)
  635.          ELSE RETURN Key.PagePtr;               (*    else next page ptr.   *)
  636.       END;
  637.    END;
  638. END GetPtr;
  639.  
  640. (****************************************************************************)
  641. (* Key locating procedures - SeekTop, SeekEnd, SeekKey                      *)
  642. (****************************************************************************)
  643.  
  644. PROCEDURE SeekTop(I : NDXFile);
  645. BEGIN
  646.    IF ErrCode # 0 THEN RETURN; END;             (* If error, abort now.     *)
  647.    ClearStack(I);                               (* Clear path to leaf level *)
  648.    I^.PNum := I^.Root;                          (* Set pointer to root page *)
  649.    GetPage(I);                                  (*    and read it from disk *)
  650.    IF ErrCode # 0 THEN RETURN; END;             (*    or abort if unable.   *)
  651.    WHILE NOT I^.LeafPage DO                     (* While not at leaf level, *)
  652.       I^.KNum := 1;                             (*    set ptr to first key  *)
  653.       PushPage(I);                              (*    and push page/key     *)
  654.       IF ErrCode # 0 THEN RETURN; END;          (*    if error, abort.      *)
  655.       I^.PNum := GetPtr(I);                     (*    Get ptr to next page  *)
  656.       GetPage(I);                               (*   and get it from disk.  *)
  657.       IF ErrCode # 0 THEN RETURN; END;          (*   or abort if unable.    *)
  658.    END;                                         (* Continue till leaf level *)
  659.    I^.Key.Key := '';                            (* Set last key found to    *)
  660.    I^.Key.RecPtr := 0;                          (*     null (top of file)   *)
  661.    I^.KNum  := 0;                               (* Set ptr to start of page *)
  662.    I^.BOF   := TRUE;                            (* Set BOF flag to true     *)
  663.    I^.EOF   := (I^.Page.NumKeys = 0);           (* If empty index, then EOF *)
  664.    I^.FOUND := FALSE;                           (* Set FOUND flag to false  *)
  665. END SeekTop;
  666.  
  667. PROCEDURE SeekEnd(I : NDXFile);
  668. BEGIN
  669.    IF ErrCode # 0 THEN RETURN; END;             (* If error, abort now.     *)
  670.    ClearStack(I);                               (* Clear path to leaf level *)
  671.    I^.PNum := I^.Root;                          (* Set ptr to root page     *)
  672.    GetPage(I);                                  (* Read root from disk.     *)
  673.    IF ErrCode # 0 THEN RETURN; END;             (* Abortif unable to read   *)
  674.    WHILE NOT I^.LeafPage DO                     (* While not at leaf level, *)
  675.       I^.KNum := I^.Page.NumKeys + 1;           (*    Set ptr to last key,  *)
  676.       PushPage(I);                              (*    and push page/key     *)
  677.       IF ErrCode # 0 THEN RETURN; END;          (*    abort if error.       *)
  678.       I^.PNum := GetPtr(I);                     (*    get ptr to next page  *)
  679.       GetPage(I);                               (*    Get next page         *)
  680.       IF ErrCode # 0 THEN RETURN; END;          (*    or abort if unable.   *)
  681.    END;                                         (* Continue till leaf level *)
  682.    Fill(ADR(I^.Key.Key), I^.KeyLen, CHR(255));  (* Set last key found to max*)
  683.    I^.Key.RecPtr := 999999999;                  (*    (to EOF)              *)
  684.    I^.KNum := I^.Page.NumKeys + 1;              (* Set ptr to EOF           *)
  685.    I^.BOF   := (I^.Page.NumKeys = 0);           (* If index empty then BOF  *)
  686.    I^.EOF   := TRUE;                            (* Set EOF true             *)
  687.    I^.FOUND := FALSE;                           (* Set FOUND false          *)
  688. END SeekEnd;
  689.  
  690. PROCEDURE CmpKey(VAR K1,K2:ARRAY OF CHAR; KeyLen:CARDINAL):INTEGER;
  691. VAR N : CARDINAL;
  692. BEGIN
  693.    FOR N := 0 TO KeyLen-1 DO
  694.       IF    K1[N] > K2[N] THEN RETURN 1;
  695.       ELSIF K1[N] < K2[N] THEN RETURN -1;
  696.       END;
  697.    END;
  698.    RETURN 0;
  699. END CmpKey;
  700.  
  701. PROCEDURE CmpNum(N1, N2 : LONGREAL) : INTEGER;
  702. BEGIN
  703.    IF    N1 > N2 THEN RETURN 1;
  704.    ELSIF N1 < N2 THEN RETURN -1;
  705.    ELSE  RETURN 0;
  706.    END;
  707. END CmpNum;
  708.  
  709. PROCEDURE ScanPage(I:NDXFile; SKey:ARRAY OF CHAR; SPtr:LONGCARD):LONGCARD;
  710. VAR Match   : INTEGER;
  711.     NumPtr1,                                    (* For numeric keys, set    *)
  712.     NumPtr2 : POINTER TO LONGREAL;              (*   template strings->real *)
  713.     Ptr     : LONGCARD;
  714. BEGIN
  715.    I^.KNum := 1;                                (* Set ptr to 1st key in pg *)
  716.    Match   := -1;                               (* Set match to none        *)
  717.    IF I^.Numeric > 0 THEN                       (* If numeric key, set ptrs *)
  718.       NumPtr1 := ADR(SKey);                     (*    to search and key     *)
  719.       NumPtr2 := ADR(I^.Key.Key);               (*    strings to template   *)
  720.    END;                                         (*    them as long reals    *)
  721.    LOOP;                                        (* Enter match loop         *)
  722.       Ptr  := GetPtr(I);                        (*   Get key from page      *)
  723.       IF I^.KNum > I^.Page.NumKeys THEN         (*   If last key in page,   *)
  724.          EXIT;                                  (*      exit and return ptr *)
  725.       END;                                      (*   else, compare keys     *)
  726.       IF I^.Numeric > 0 THEN                    (*      and set Match to    *)
  727.          Match := CmpNum(NumPtr2^, NumPtr1^);   (*      -1, 1, or 0 based   *)
  728.       ELSE                                      (*      on comparison.      *)
  729.          Match:=CmpKey(I^.Key.Key,SKey,I^.KeyLen);
  730.       END;
  731.       IF (Match = 0) AND (SPtr > 0) THEN        (*   If looking for exact   *)
  732.          IF (I^.Key.RecPtr > SPtr)              (*      match, then compare *)
  733.             THEN Match := 1;                    (*      record pointer to   *)
  734.          ELSIF (I^.Key.RecPtr < SPtr)           (*      search record ptr   *)
  735.             THEN Match := -1;                   (*      too, and adjust     *)
  736.          END;                                   (*      results accordingly *)
  737.       END;                                      (*   Else, ignore rec ptr   *)
  738.       IF Match >= 0                             (* If key >= searchkey      *)
  739.          THEN EXIT;                             (*    exit and return ptr   *)
  740.          ELSE INC(I^.KNum);                     (*    else bump to next key *)
  741.       END;                                      (*         in page.         *)
  742.    END; (* Loop *)                              (* Continue for all keys    *)
  743.    I^.FOUND := (Match = 0);                     (* Set exact match flag     *)
  744.    RETURN Ptr;                                  (* Return record/page ptr.  *)
  745. END ScanPage;
  746.  
  747. PROCEDURE SeekKey(I:NDXFile; Key:ARRAY OF CHAR; Ptr:LONGCARD);
  748. VAR SKey    : ARRAY [0..MaxKeyLen] OF CHAR;
  749.     NextPtr : LONGCARD;
  750. BEGIN
  751.    IF ErrCode # 0 THEN RETURN; END;             (* If error, abort now.     *)
  752.    IF I^.Numeric > 0 THEN                       (* If numeric key, copy it  *)
  753.       Move(ADR(Key), ADR(SKey), 8);             (*    to search key string  *)
  754.    ELSE                                         (* Else if character key,   *)
  755.       Copy(SKey, Key);                          (*    make working copy     *)
  756.       PadRight(SKey, I^.KeyLen);                (*    convert to dBase fmt  *)
  757.    END;
  758.    ClearStack(I);                               (* Clear tree path stack.   *)
  759.    I^.PNum := I^.Root;                          (* Start with root page.    *)
  760.    REPEAT                                       (* For all pages in path,   *)
  761.      GetPage(I);                                (*   Get page from disk,    *)
  762.      IF ErrCode # 0 THEN RETURN; END;           (*   or abort if unable     *)
  763.      NextPtr := ScanPage(I, SKey, Ptr);         (*   Scan page for next ptr *)
  764.      IF NOT I^.LeafPage THEN                    (*   If page was a node,    *)
  765.         PushPage(I);                            (*      Update path stack,  *)
  766.         IF ErrCode # 0 THEN RETURN; END;        (*      if error, abort.    *)
  767.         I^.PNum := NextPtr;                     (*      and cont w/new page *)
  768.      END;                                       (*   Continue for all nodes *)
  769.    UNTIL I^.LeafPage;                           (* Until leaf is reached.   *)
  770.    I^.BOF := I^.Page.NumKeys = 0;               (* Set Top of file status.  *)
  771.    I^.EOF := (I^.KNum > I^.Page.NumKeys);       (* Set End of File status.  *)
  772.    IF I^.BOF THEN                               (* If at beginning of file, *)
  773.       I^.Key.Key := '';                         (*    Set key found to      *)
  774.       I^.Key.RecPtr := 0;                       (*    smallest possible key *)
  775.    ELSIF I^.EOF THEN                            (* If at end of file,       *)
  776.      Fill(ADR(I^.Key.Key), I^.KeyLen, CHR(255));(*    Set key found to      *)
  777.      I^.Key.RecPtr := 9999999999;               (*    largest possible key. *)
  778.    ELSE                                         (* Otherwise,               *)
  779.       Ptr := GetPtr(I);                         (*    Get key from page.    *)
  780.    END;
  781. END SeekKey;
  782.  
  783. (****************************************************************************)
  784. (* Exported procedures for searching                                        *)
  785. (****************************************************************************)
  786.  
  787. PROCEDURE GoTop(I : NDXFile);
  788. BEGIN
  789.    ErrCode := 0;                                (* Init result code         *)
  790.    ReadLock(I);                                 (* Read lock index.         *)
  791.    UpdateHeader(I);                             (* Get current index header *)
  792.    SeekTop(I);                                  (* Go to beginning of index *)
  793.    ReadUnLock(I);                               (* Release lock on index.   *)
  794. END GoTop;
  795.  
  796. PROCEDURE GoBottom(I : NDXFile);
  797. BEGIN
  798.    ErrCode := 0;                                (* Init result code         *)
  799.    ReadLock(I);                                 (* Read lock index.         *)
  800.    UpdateHeader(I);                             (* Get current index header *)
  801.    SeekEnd(I);                                  (* Go to end of index       *)
  802.    ReadUnLock(I);                               (* Release lock on index.   *)
  803. END GoBottom;
  804.  
  805. PROCEDURE Find(I : NDXFile; Key : ARRAY OF CHAR) : LONGCARD;
  806. BEGIN
  807.    ErrCode := 0;                                (* Init result code         *)
  808.    ReadLock(I);                                 (* Read lock index.         *)
  809.    UpdateHeader(I);                             (* Get current index header *)
  810.    SeekKey(I, Key, 0);                          (* Search index for key     *)
  811.    ReadUnLock(I);                               (* Release lock on index.   *)
  812.    RETURN I^.Key.RecPtr;                        (* Return key found         *)
  813. END Find;
  814.  
  815. PROCEDURE FindNKey(I : NDXFile; Num : LONGREAL) : LONGCARD;
  816. VAR NumStr : ARRAY [0..7] OF CHAR;
  817. BEGIN
  818.    ErrCode := 0;                                (* Init result code         *)
  819.    ReadLock(I);                                 (* Read lock index.         *)
  820.    UpdateHeader(I);                             (* Get current index header *)
  821.    Move(ADR(Num), ADR(NumStr), 8);              (* Copy real to key string  *)
  822.    SeekKey(I, NumStr, 0);                       (* Search index for key     *)
  823.    ReadUnLock(I);                               (* Release index lock.      *)
  824.    RETURN I^.Key.RecPtr;                        (* Return key found.        *)
  825. END FindNKey;
  826.  
  827. PROCEDURE Next (I : NDXFile) : LONGCARD;
  828. BEGIN
  829.   ErrCode := 0;                                 (* Init result code         *)
  830.   ReadLock(I);                                  (* Read lock index.         *)
  831.   UpdateHeader(I);                              (* Get current index header *)
  832.   IF I^.CHANGED THEN                            (* If index has changed     *)
  833.      SeekKey(I, I^.Key.Key, I^.Key.RecPtr+1);   (*    find next key         *)
  834.      RETURN I^.Key.RecPtr;                      (*    and return rec ptr    *)
  835.   END;                                          (* Else index not changed   *)
  836.   IF I^.EOF THEN                                (* If at end of NDX         *)
  837.      ReadUnLock(I);                             (*    Unlock NDX (if locked)*)
  838.      I^.BOF   := (I^.Page.NumKeys = 0);         (*    Set BOF flag          *)
  839.      I^.FOUND := FALSE;                         (*    Set FOUND flag false  *)
  840.      RETURN 0;                                  (*    and return 0          *)
  841.   END;                                          (* Else, may be another key *)
  842.   INC(I^.KNum);                                 (* Point to next in page.   *)
  843.   IF I^.KNum <= I^.Page.NumKeys THEN            (* If next key in cur page, *)
  844.      ReadUnLock(I);                             (*    Release NDX if locked *)
  845.      I^.BOF   := FALSE;                         (*    Set BOF flag false    *)
  846.      I^.EOF   := FALSE;                         (*    Set EOF flag false    *)
  847.      I^.FOUND := TRUE;                          (*    Set FOUND flag true   *)
  848.      RETURN GetPtr(I);                          (*    return record ptr     *)
  849.   END;                                          (* Else key not on cur page *)
  850.   IF I^.SPtr > 0 THEN                           (* If more leaves in index, *)
  851.      REPEAT                                     (*    Pop until node w/more *)
  852.         PopPage(I);                             (*    keys is reached.      *)
  853.         IF ErrCode # 0 THEN RETURN 0; END;      (*    or page number error. *)
  854.         GetPage(I);                             (*    Read ancestor page    *)
  855.         IF ErrCode # 0 THEN RETURN 0; END;      (*    if error, abort now.  *)
  856.      UNTIL (I^.SPtr = 0) OR                     (*    Continue till top of  *)
  857.            (I^.KNum <= I^.Page.NumKeys);        (*    tree (root) reached.  *)
  858.   END;                                          (* Continue till root or    *)
  859.   IF (I^.KNum > I^.Page.NumKeys) THEN           (* node with more keys hit  *)
  860.      SeekEnd(I);                                (* If no more keys, go EOF  *)
  861.      ReadUnLock(I);                             (*    Unlock index          *)
  862.      RETURN 0;                                  (*    and return null now.  *)
  863.   END;                                          (* Else, node with more keys*)
  864.   INC(I^.KNum);                                 (*    bump to next key.     *)
  865.   WHILE (NOT I^.LeafPage) DO                    (* Push setting path to 1st *)
  866.      PushPage(I);                               (*    key of each page      *)
  867.      I^.PNum := GetPtr(I);                      (*    get next page number  *)
  868.      I^.KNum := 1;                              (*    till leaf level hit.  *)
  869.      GetPage(I);                                (*    (next leaf key found) *)
  870.      IF ErrCode # 0 THEN RETURN 0; END;         (*    or till error reading *)
  871.   END;                                          (* With next key found,     *)
  872.   ReadUnLock(I);                                (*    Release index,        *)
  873.   I^.EOF   := FALSE;                            (*    Set EOF flag false    *)
  874.   I^.BOF   := FALSE;                            (*    Set BOF flag false    *)
  875.   I^.FOUND := TRUE;                             (*    Set FOUND flag true   *)
  876.   RETURN GetPtr(I);                             (*    Return record pointer *)
  877. END Next;
  878.  
  879. PROCEDURE Prev (I : NDXFile) : LONGCARD;
  880. VAR Ptr : LONGCARD;
  881. BEGIN
  882.    ErrCode := 0;                                (* Init result code         *)
  883.    ReadLock(I);                                 (* Read lock index.         *)
  884.    UpdateHeader(I);                             (* Get current index header *)
  885.    IF I^.CHANGED THEN                           (* If index changed, restore*)
  886.       SeekKey(I, I^.Key.Key, I^.Key.RecPtr);    (*    path to last key      *)
  887.    END;                                         (* Now index set as before. *)
  888.    IF I^.KNum > 1 THEN                          (* If more keys in cur page *)
  889.       ReadUnLock(I);                            (*    unlock index          *)
  890.       DEC(I^.KNum);                             (*    bump key number       *)
  891.       I^.BOF   := FALSE;                        (*    set BOF false         *)
  892.       I^.EOF   := FALSE;                        (*    set EOF false         *)
  893.       I^.FOUND := TRUE;                         (*    set FOUND true        *)
  894.       RETURN GetPtr(I);                         (*    return record ptr     *)
  895.    END;                                         (* Else key not on cur page *)
  896.    IF I^.BOF THEN                               (* If at top of NDX         *)
  897.       ReadUnLock(I);                            (*    Unlock NDX (if locked)*)
  898.       I^.EOF   := (I^.Page.NumKeys = 0);        (*    Set EOF flag          *)
  899.       I^.FOUND := FALSE;                        (*    Set FOUND flag false  *)
  900.       ErrCode := 0;                             (*    Set result code = OK  *)
  901.       RETURN 0;                                 (*    and return 0          *)
  902.    END;                                         (* Else, key on prev leaf.  *)
  903.    WHILE (I^.KNum <= 1) AND (I^.SPtr > 0) DO    (* Pop pages till page with *)
  904.       PopPage(I);                               (*    more keys found or    *)
  905.    END;                                         (*    continue till root hit*)
  906.    IF I^.KNum <= 1 THEN                         (* If no more keys found,   *)
  907.       SeekTop(I);                               (*    go to top of index,   *)
  908.       ReadUnLock(I);                            (*    so unlock index,      *)
  909.       RETURN 0;                                 (*    and return 0          *)
  910.    END;                                         (* Else, more keys found so *)
  911.    GetPage(I);                                  (*    Read page with keys   *)
  912.    IF ErrCode # 0 THEN RETURN 0; END;           (*    or abort if error.    *)
  913.    DEC(I^.KNum);                                (*    bump to prev key      *)
  914.    WHILE NOT I^.LeafPage DO                     (* Push pages setting path  *)
  915.       PushPage(I);                              (*    to last key of each pg*)
  916.       I^.PNum := GetPtr(I);                     (*    Save page and key #s  *)
  917.       GetPage(I);                               (*    until leaf level      *)
  918.       IF ErrCode # 0 THEN RETURN 0; END;        (*    or abort if error.    *)
  919.       I^.KNum := I^.Page.NumKeys + 1;           (*    Set ptr to last key   *)
  920.    END;                                         (* Now prev leaf found.     *)
  921.    ReadUnLock(I);                               (*    so release index,     *)
  922.    DEC(I^.KNum);                                (*    Point to last key     *)
  923.    I^.BOF   := FALSE;                           (*    Set BOF flag false    *)
  924.    I^.EOF   := FALSE;                           (*    Set EOF flag false    *)
  925.    I^.FOUND := TRUE;                            (*    Set FOUND flag true   *)
  926.    RETURN GetPtr(I);                            (*    Return record ptr     *)
  927. END Prev;
  928.  
  929. (****************************************************************************)
  930. (* Exported procedures for checking search status                           *)
  931. (****************************************************************************)
  932.  
  933. PROCEDURE BOF(I : NDXFile) : BOOLEAN;
  934. BEGIN
  935.    ErrCode := 0;                                (* Init result code         *)
  936.    RETURN I^.BOF;                               (* Return BOF flag          *)
  937. END BOF;
  938.  
  939. PROCEDURE EOF(I : NDXFile) : BOOLEAN;
  940. BEGIN
  941.    ErrCode := 0;                                (* Init result code         *)
  942.    RETURN I^.EOF;                               (* Return EOF flag          *)
  943. END EOF;
  944.  
  945. PROCEDURE FOUND(I : NDXFile) : BOOLEAN;
  946. BEGIN
  947.    ErrCode := 0;                                (* Init result code         *)
  948.    RETURN I^.FOUND;                             (* Return FOUND flag        *)
  949. END FOUND;
  950.  
  951. (****************************************************************************)
  952. (* Exported procedures for adding & deleting keys                           *)
  953. (****************************************************************************)
  954.  
  955. PROCEDURE AddKey(I : NDXFile; Key : ARRAY OF CHAR; Ptr : LONGCARD);
  956. VAR SKey     : ARRAY[0..MaxKeyLen] OF CHAR;
  957.     Next     : LONGCARD;
  958.     RecPtr   : LONGCARD;
  959.     FPtr     : LONGCARD;
  960.     New      : PageType;
  961.     N        : CARDINAL;
  962.  
  963.    PROCEDURE InsertKey();
  964.    VAR SKey, SPos, Num : CARDINAL;
  965.        KPtr            : KeyPtr;
  966.    BEGIN
  967.      IF ErrCode # 0 THEN RETURN; END;           (* If error, abort now.     *)
  968.      SKey := I^.KNum - 1;                       (* Calc location in page    *)
  969.      SPos := SKey * I^.KeySize;                 (*  for key to be inserted  *)
  970.      Num := (I^.Page.NumKeys-SKey)*I^.KeySize+8;(*  and # of bytes to shift *)
  971.      WHILE Num > 0 DO                           (* Shift all higher keys up *)
  972.         DEC(Num);                               (* to make room for new one.*)
  973.         I^.Page.Keys[SPos + Num + I^.KeySize] :=
  974.         I^.Page.Keys[SPos + Num];
  975.      END;
  976.      KPtr := ADR(I^.Page.Keys[SPos]);           (* Set ptr to new key pos   *)
  977.      KPtr^.RecPtr := I^.Key.RecPtr;             (* Copy in new rec pointer  *)
  978.      KPtr^.PagePtr:= I^.Key.PagePtr;            (* Copy in new page pointer *)
  979.      Move(ADR(I^.Key.Key), ADR(KPtr^.Key), I^.KeyLen); (* Copy in new key   *)
  980.      INC(I^.Page.NumKeys);                      (* Bump # of keys in page   *)
  981.    END InsertKey;
  982.  
  983.    PROCEDURE Split();
  984.    VAR N, MKey, MPos, KPos : CARDINAL;
  985.    BEGIN
  986.      IF ErrCode # 0 THEN RETURN; END;           (* If error, abort now.     *)
  987.      New  := I^.Page;                           (* Make a copy of the page  *)
  988.      MKey := I^.Page.NumKeys DIV 2;             (* Calculate middle key     *)
  989.      MPos := (I^.Page.NumKeys-MKey)*I^.KeySize; (* Set pointer to middle key*)
  990.      New.NumKeys := I^.Page.NumKeys - MKey;     (* Lower page gets 1/2 keys *)
  991.      I^.Page.NumKeys := MKey;                   (* Upper page gets rest     *)
  992.      FOR N := MPos TO HIGH(I^.Page.Keys) DO     (* Shift high keys down on  *)
  993.        I^.Page.Keys[N-MPos] := I^.Page.Keys[N]; (*   upper (original) page  *)
  994.      END;
  995.      PutPage(I);                                (* Write upper page to disk *)
  996.      IF ErrCode # 0 THEN RETURN; END;           (* If error writing, abort  *)
  997.      KPos := (New.NumKeys-1) * I^.KeySize;      (* Set ptr to last key on   *)
  998.      I^.KPtr := ADR(New.Keys[KPos]);            (*   the low page (mid key) *)
  999.      Copy(I^.Key.Key, I^.KPtr^.Key);            (* Make a copy of the middle*)
  1000.      I^.Key.RecPtr := 0;                        (*   key, to pass to parent *)
  1001.      IF NOT I^.LeafPage THEN                    (* If page was a node, then *)
  1002.         DEC(New.NumKeys);                       (*   don't save middle key  *)
  1003.      END;                                       (*   value, only pointer    *)
  1004.      I^.Page := New;                            (* Prepare to write lower   *)
  1005.      I^.PNum := I^.NextFree;                    (* (pointer to pass up to   *)
  1006.      PutPage(I);                                (* Write new page to disk.  *)
  1007.      IF ErrCode # 0 THEN RETURN; END;           (* If error writing, abort  *)
  1008.      I^.Key.PagePtr := I^.NextFree;             (*   parent node w/mid key) *)
  1009.      INC(I^.NextFree);                          (* Bump next free page #    *)
  1010.   END Split;
  1011.  
  1012.   PROCEDURE NewRoot();
  1013.   BEGIN
  1014.      IF ErrCode # 0 THEN RETURN; END;           (* If error, abort now.     *)
  1015.      InitPage(I^.Page);                         (* Initialize new root page *)
  1016.      I^.KNum := 1;                              (*   and first key (pointer *)
  1017.      InsertKey();                               (*   to the lower page)     *)
  1018.      I^.KNum := 2;                              (*   Second key is pointer  *)
  1019.      I^.Key.PagePtr := I^.Root;                 (*   to the higher page     *)
  1020.      I^.Key.RecPtr  := 0;                       (*   Old root               *)
  1021.      InsertKey();                               (* Since this is a node,    *)
  1022.      I^.Page.NumKeys := 1;                      (*   last key is ptr only   *)
  1023.      I^.Root := I^.NextFree;                    (* Get page number for new  *)
  1024.      INC(I^.NextFree);                          (*   root and bump nextfree *)
  1025.      I^.PNum := I^.Root;                        (* Write new root node to   *)
  1026.      PutPage(I);                                (*   disk.                  *)
  1027.   END NewRoot;
  1028.  
  1029. BEGIN
  1030.    ErrCode := 0;                                (* Init result code         *)
  1031.    Copy(SKey, Key);                             (* Make working copy of key *)
  1032.    PadRight(SKey, I^.KeyLen);                   (* Convert to dBase string  *)
  1033.    WriteLock(I);                                (* Write lock index.        *)
  1034.    UpdateHeader(I);                             (* Get current index header *)
  1035. (* Traverse tree down to leaf level *)
  1036.    ClearStack(I);                               (* Clear tree path stack.   *)
  1037.    I^.PNum := I^.Root;                          (* Start with root page.    *)
  1038.    GetPage(I);                                  (* Get root page from disk. *)
  1039.    IF ErrCode # 0 THEN RETURN; END;             (* If root error, abort now *)
  1040.    WHILE (NOT I^.LeafPage) AND (ErrCode = 0) DO (* While not at leaf level, *)
  1041.       Next := ScanPage(I, SKey, Ptr);           (*   get next page number.  *)
  1042.       PushPage(I);                              (*   push current page/key  *)
  1043.       I^.PNum := Next;                          (*   Make next page current *)
  1044.       GetPage(I);                               (*   Get page from disk,    *)
  1045.       IF ErrCode # 0 THEN RETURN; END;          (*   If error, abort now.   *)
  1046.    END;                                         (* continue till leaf page  *)
  1047. (* Prepare new key for insertion into tree.     *)
  1048.    RecPtr := ScanPage(I, SKey, Ptr);            (* find pos for new key     *)
  1049.    Copy(I^.Key.Key, SKey);                      (* Load new key string      *)
  1050.    I^.Key.RecPtr := Ptr;                        (* Load new key record ptr  *)
  1051.    I^.Key.PagePtr:= 0;                          (* Set new key's page ptr   *)
  1052. (* Enter key in tree and re-organize as needed. *)
  1053.    LOOP;
  1054.       IF ErrCode # 0 THEN RETURN; END;          (* If error, abort now.     *)
  1055.       InsertKey();                              (* Add key to cur page.     *)
  1056.       IF I^.Page.NumKeys <= I^.KeysPerPage THEN (* If no overflow occurred, *)
  1057.          PutPage(I);                            (*    replace cur page and  *)
  1058.          EXIT;                                  (*    exit loop now.        *)
  1059.       END;                                      (* Else if page overflowed, *)
  1060.       Split();                                  (*    split current page.   *)
  1061.       IF I^.SPtr = 0 THEN                       (* If root page was split,  *)
  1062.          NewRoot();                             (*    create a new root     *)
  1063.          EXIT;                                  (*    and exit loop.        *)
  1064.       ELSE                                      (* Otherwise,               *)
  1065.          PopPage(I);                            (*    get no. of ancestor.  *)
  1066.          GetPage(I);                            (*    get ancestor from disk*)
  1067.       END;                                      (*    continue looping      *)
  1068.    END;                                         (*    pushing new key up    *)
  1069.    INC(I^.Changes);                             (* Set NDX changed flag     *)
  1070.    IF I^.Shared THEN                            (* If in multi-user mode,   *)
  1071.       WriteHeader(I);                           (*    write changes to NDX  *)
  1072.    END;                                         (*    header.               *)
  1073.    WriteUnLock(I);                              (* Release write lock       *)
  1074.    ReadLock(I);                                 (* Read lock index.         *)
  1075.    SeekKey(I, SKey, Ptr);                       (* Set path to new key.     *)
  1076.    ReadUnLock(I);                               (* Release index and return *)
  1077. END AddKey;
  1078.  
  1079. PROCEDURE AddNKey(I : NDXFile; Key : LONGREAL; Ptr : LONGCARD);
  1080. VAR KeyStr  : ARRAY [0..7] OF CHAR;
  1081. BEGIN
  1082.    Move(ADR(Key), ADR(KeyStr), 8);
  1083.    AddKey(I, KeyStr, Ptr);
  1084. END AddNKey;
  1085.  
  1086. PROCEDURE DelKey(I : NDXFile);              (* Delete last key found    *)
  1087.    PROCEDURE RemoveKey();
  1088.    VAR CPos, SPos, Num : CARDINAL;
  1089.        KPtr            : KeyPtr;
  1090.    BEGIN
  1091.      SPos := (I^.KNum-1) * I^.KeySize;          (*   For key to be deleted, *)
  1092.      Num:=(I^.Page.NumKeys-I^.KNum)*I^.KeySize+8;
  1093.      CPos   := 0;
  1094.      WHILE CPos < Num DO
  1095.         I^.Page.Keys[SPos + CPos] :=
  1096.         I^.Page.Keys[SPos + CPos + I^.KeySize];
  1097.         INC(CPos);
  1098.      END;
  1099.      DEC(I^.Page.NumKeys);
  1100.    END RemoveKey;
  1101.  
  1102. BEGIN
  1103.    ErrCode := 0;                                (* Init result code         *)
  1104.    IF NOT I^.FOUND THEN RETURN; END;            (* If no cur key, were done *)
  1105.    WriteLock(I);                                (* Write lock index.        *)
  1106.    UpdateHeader(I);                             (* Get current index header *)
  1107.    IF I^.CHANGED THEN                           (* If structure changed,    *)
  1108.       SeekKey(I, I^.Key.Key, I^.Key.RecPtr);    (*   Set path to last key,  *)
  1109.       IF NOT I^.FOUND THEN RETURN; END;         (* If already deleted, done.*)
  1110.    END;                                         (* Else with path set to    *)
  1111.    LOOP                                         (*   last key found,        *)
  1112.       IF ErrCode # 0 THEN RETURN; END;          (* If error, abort now.     *)
  1113.       RemoveKey();                              (* Remove key from page     *)
  1114.       PutPage(I);                               (* and write page to disk.  *)
  1115.       IF (I^.Page.NumKeys > 0) OR (I^.SPtr = 0) (* If more keys in page,    *)
  1116.          THEN EXIT;                             (*    or at root, we're done*)
  1117.       ELSE                                      (* Else remove ancestor ptr *)
  1118.          PopPage(I);                            (*    Get ancestor pointer  *)
  1119.          GetPage(I);                            (*    Retrieve from disk,   *)
  1120.       END;
  1121.    END; (* Delete loop *)
  1122.    INC(I^.Changes);                             (* Set NDX changed flag     *)
  1123.    IF I^.Shared THEN                            (* If in multi-user mode,   *)
  1124.       WriteHeader(I);                           (*    update NDX header,    *)
  1125.    END;
  1126.    WriteUnLock(I);                              (* Release write lock       *)
  1127.    ReadLock(I);                                 (* Read lock index          *)
  1128.    SeekKey(I, I^.Key.Key, I^.Key.RecPtr);       (* Set path to next key,    *)
  1129.    ReadUnLock(I);                               (* Release index and return *)
  1130. END DelKey;
  1131.  
  1132. (****************************************************************************)
  1133. (* Exported procedures for opening, closing, and creating indices           *)
  1134. (****************************************************************************)
  1135.  
  1136. PROCEDURE OpenNDX (VAR I : NDXFile; FileName : ARRAY OF CHAR);
  1137. VAR TempIOcheck : BOOLEAN;
  1138.     TempShareMode : BITSET;
  1139. BEGIN
  1140.   ErrCode := 0;                                 (* Init result code         *)
  1141.   IF NOT Available(SIZE(NDXRec)) THEN           (* If not enough memory,    *)
  1142.      HandleError('OpenNDX', I, ErrMemory);      (*    display error message *)
  1143.      RETURN;                                    (*    and abort opening NDX *)
  1144.   END;                                          (* Else with memory availble*)
  1145.   ALLOCATE(I, SIZE(NDXRec));                    (*    Allocate NDX memory   *)
  1146.   Copy(I^.Name, FileName);                      (* Copy in file name to I   *)
  1147.   Caps(I^.Name);                                (* Convert to upper case.   *)
  1148.   IF (Pos(I^.Name, '.')) > (HIGH(I^.Name)) THEN (* Check for file extension *)
  1149.      Append(I^.Name, '.NDX');                   (* If none, append default  *)
  1150.   END;                                          (*    extension of '.NDX'   *)
  1151.   I^.Shared   := MultiUser;                     (* Set sharing mode for NDX *)
  1152.   I^.RDLOCKED := FALSE;                         (* Set read locked to false *)
  1153.   I^.WRLOCKED := FALSE;                         (* Set write locked false   *)
  1154.   I^.CHANGED  := FALSE;                         (* Set changed flag false   *)
  1155.   I^.OPEN     := FALSE;                         (* Set file open flag false *)
  1156.   TempShareMode := FIO.ShareMode;               (* Save previous share mode *)
  1157.   IF MultiUser                                  (* If in multi-user mode,   *)
  1158.      THEN FIO.ShareMode := FIO.ShareDenyNone;   (* Setup for shared access  *)
  1159.      ELSE FIO.ShareMode := FIO.ShareDenyRW;     (* Else setup for excluseive*)
  1160.   END;                                          (*    (Single user) access  *)
  1161.   TempIOcheck := FIO.IOcheck;                   (* Save IOchecking status   *)
  1162.   FIO.IOcheck := FALSE;                         (* Turn off IO err checking *)
  1163.   I^.Handle := FIO.Open(I^.Name);               (* Attempt to open file.    *)
  1164.   FIO.ShareMode := TempShareMode;               (* Restore sharing mode     *)
  1165.   IF FIO.IOresult() > 0 THEN                    (* If error opening file,   *)
  1166.      DEALLOCATE(I, SIZE(NDXRec));               (*    return memory used,   *)
  1167.      FIO.IOcheck   := TempIOcheck;              (*    restore err checking  *)
  1168.      HandleError('OpenNDX', I, ErrOpen);        (*    display error message *)
  1169.      RETURN;                                    (*    and abort opening NDX *)
  1170.   END;                                          (* Else, index opened OK,   *)
  1171.   I^.OPEN := TRUE;                              (* Set NDX open flag        *)
  1172.   ReadHeader(I);                                (* Read header from disk    *)
  1173.   IF ErrCode # 0 THEN                           (* If error reading header, *)
  1174.      FIO.Close(I^.Handle);                      (*    close NDX file,       *)
  1175.      DEALLOCATE(I, SIZE(NDXRec));               (*    return memory used,   *)
  1176.      FIO.IOcheck := TempIOcheck;                (*    restore err checking  *)
  1177.      RETURN;                                    (*    and abort procedure.  *)
  1178.   END;                                          (* Else header read OK.     *)
  1179.   IF (I^.KeyLen > MaxKeyLen) OR                 (* If header data is not    *)
  1180.      (I^.KeyLen = 0) OR                         (*    valid for a dBase     *)
  1181.      (I^.KeySize < I^.KeyLen + 8) OR            (*    index file, (i.e.     *)
  1182.      ((I^.KeySize MOD 2) # 0) OR                (*    Key length or size or *)
  1183.      (I^.KeysPerPage < 4) OR                    (*    keys per page is set  *)
  1184.      (I^.KeysPerPage > 50) THEN                 (*    to an illegal value,  *)
  1185.      HandleError('OpenNDX', I, ErrBadNDX);      (*    display error message *)
  1186.      FIO.Close(I^.Handle);                      (*    close index file,     *)
  1187.      DEALLOCATE(I, SIZE(NDXRec));               (*    return memory used,   *)
  1188.      FIO.IOcheck := TempIOcheck;                (*    restore err checking  *)
  1189.      RETURN;                                    (*    and abort procedure.  *)
  1190.   END;                                          (* Else NDX header is OK.   *)
  1191.   FIO.IOcheck := TempIOcheck;                   (* Restore IO err checking  *)
  1192.   GoTop(I);                                     (* Init to start of NDX     *)
  1193. END OpenNDX;
  1194.  
  1195. PROCEDURE CloseNDX(VAR I : NDXFile);
  1196. VAR TempIOcheck : BOOLEAN;
  1197. BEGIN
  1198.    ErrCode := 0;                                (* Init result code         *)
  1199.    IF NOT I^.Shared THEN                        (* If in single user mode,  *)
  1200.       WriteHeader(I);                           (*    update index header   *)
  1201.    END;                                         (*    before closing.       *)
  1202.    TempIOcheck := FIO.IOcheck;                  (* Save IOchecking status   *)
  1203.    FIO.IOcheck := FALSE;                        (* Turn off IO err checking *)
  1204.    FIO.Close(I^.Handle);                        (* Close index file.        *)
  1205.    FIO.IOcheck := TempIOcheck;                  (* Restore IO error checking*)
  1206.    IF FIO.IOresult() > 0 THEN                   (* If error closing file,   *)
  1207.       HandleError('CloseNDX', I, ErrClose);     (*    set error return code *)
  1208.    END;                                         (* Either way,              *)
  1209.    DEALLOCATE(I, SIZE(NDXRec));                 (* Return memory used.      *)
  1210. END CloseNDX;
  1211.  
  1212. PROCEDURE CreateNDX(VAR I    : NDXFile; FileName : ARRAY OF CHAR;
  1213.                     KeyField : ARRAY OF CHAR;
  1214.                     KeyType  : CHAR;
  1215.                     KeyLen   : CARDINAL);
  1216. VAR N : CARDINAL;                               (* For initializing root    *)
  1217.     TempIOcheck : BOOLEAN;                      (* FIO IOcheck mode storage *)
  1218.     TempShareMode : BITSET;                     (* FIO Share mode storage   *)
  1219. BEGIN
  1220.   ErrCode := 0;                                 (* Init result code         *)
  1221.   IF NOT Available(SIZE(NDXRec)) THEN           (* If not enough memory,    *)
  1222.      HandleError('CreateNDX', I, ErrMemory);    (*    display error message *)
  1223.      RETURN;                                    (*    and abort procedure.  *)
  1224.   END;                                          (* Else, with mem available,*)
  1225.   ALLOCATE(I, SIZE(NDXRec));                    (*    allocate memory.      *)
  1226.   I^.OPEN := FALSE;                             (* Initialize open and lockd*)
  1227.   I^.RDLOCKED := FALSE;                         (*    Read locked to false  *)
  1228.   I^.WRLOCKED := FALSE;                         (*    Write locked to false *)
  1229.   Caps(FileName);                               (* Convert to upper case.   *)
  1230.   Copy(I^.Name, FileName);                      (* Copy in file name to I   *)
  1231.   IF Pos('.', I^.Name) > HIGH(I^.Name) THEN     (* Check for file extension *)
  1232.      Append(I^.Name, '.NDX');                   (* If none, append default  *)
  1233.   END;                                          (*    extension of '.NDX'   *)
  1234.   TempIOcheck := FIO.IOcheck;                   (* Save IOchecking status   *)
  1235.   FIO.IOcheck := FALSE;                         (* Turn off IO err checking *)
  1236.   I^.Handle := FIO.Create(I^.Name);             (* Create index file        *)
  1237.   FIO.IOcheck := TempIOcheck;                   (* Restore IO error checking*)
  1238.   IF FIO.IOresult() # 0 THEN                    (* If error creating index, *)
  1239.      DEALLOCATE(I, SIZE(NDXRec));               (*    return memory used,   *)
  1240.      HandleError('CreateNDX', I, ErrOpen);      (*    display error message *)
  1241.      RETURN;                                    (*    and abort procedure.  *)
  1242.   END;                                          (* Else with NDX created    *)
  1243.   I^.OPEN        := TRUE;                       (* Set file open flag       *)
  1244.   I^.Root        := 1;                          (* Initialize root pointer  *)
  1245.   I^.NextFree    := 2;                          (* Init Next free page ptr  *)
  1246.   I^.Dummy1      := 0;                          (* Init dummy fields to 0   *)
  1247.   IF (KeyType = 'N') OR (KeyType = 'D')         (* If key type is numeric,  *)
  1248.      THEN I^.Numeric := 1;                      (*    set numeric flag      *)
  1249.      ELSE I^.Numeric := 0;                      (* Else clear numeric flag  *)
  1250.   END;                                          (* Either way, set length   *)
  1251.   I^.KeyLen := KeyLen;                          (*    as specified by caller*)
  1252.   IF ((KeyLen MOD 4) > 0)                       (* Calculate value of key   *)
  1253.      THEN I^.KeySize := KeyLen + 8 +            (*    size (Key string len  *)
  1254.                         (4 - (KeyLen MOD 4));   (*    + length of key ptrs) *)
  1255.      ELSE I^.KeySize := KeyLen + 8;             (*    for compatibility     *)
  1256.   END;                                          (*    with dBase NDX files  *)
  1257.   I^.KeysPerPage := 508 DIV I^.KeySize;         (* Init Keys per page       *)
  1258.   I^.Dummy2      := 0;                          (* Init dummy fields to 0   *)
  1259.   I^.Unique      := 0;                          (* Init unique key to FALSE *)
  1260.   Copy(I^.KeyField, KeyField);                  (* Copy in key expression   *)
  1261.   WriteHeader(I);                               (* Write new index header.  *)
  1262.   IF ErrCode # 0 THEN                           (* If error writing header, *)
  1263.      FIO.IOcheck := FALSE;                      (*    turn off IO err chkng *)
  1264.      FIO.Close(I^.Handle);                      (*    Close file.           *)
  1265.      FIO.IOcheck := TempIOcheck;                (*    restore IO err chking *)
  1266.      DEALLOCATE(I, SIZE(NDXRec));               (*    return memory used    *)
  1267.      RETURN;                                    (*    and abort procedure.  *)
  1268.   END;                                          (* Else w/header written,   *)
  1269.   I^.Page.NumKeys := 0;                         (* Init root page           *)
  1270.   FOR N := 0 TO HIGH(I^.Page.Keys) DO           (*    Set # of keys to 0    *)
  1271.      I^.Page.Keys[N] := 0C;                     (*    and clear page.       *)
  1272.   END;                                          (* with root page initializd*)
  1273.   I^.PNum := I^.Root;                           (* Set cur page to root,    *)
  1274.   I^.KNum := 0;                                 (* Set cur key to none,     *)
  1275.   PutPage(I);                                   (* Write page to disk,      *)
  1276.   IF ErrCode # 0 THEN RETURN; END;              (* If err writing root abort*)
  1277.   FIO.IOcheck := FALSE;                         (* Else, turn off err chking*)
  1278.   FIO.Close(I^.Handle);                         (* Close new index file,    *)
  1279.   FIO.IOcheck := TempIOcheck;                   (* Restore IO err checking  *)
  1280.   DEALLOCATE(I, SIZE(NDXRec));                  (* Return memory used.      *)
  1281. END CreateNDX;
  1282.  
  1283. (****************************************************************************)
  1284. (* Miscellaneous exported procedures                                        *)
  1285. (****************************************************************************)
  1286.  
  1287. PROCEDURE Unique(I : NDXFile) : BOOLEAN;
  1288. BEGIN
  1289.    ErrCode := 0;                                (* Set result code = OK     *)
  1290.    RETURN I^.Unique = 1;                        (* Return Unique flag       *)
  1291. END Unique;
  1292.  
  1293. PROCEDURE KeyField(I : NDXFile; VAR Field : ARRAY OF CHAR);
  1294. BEGIN
  1295.    ErrCode := 0;                                (* Set result code = OK     *)
  1296.    Copy(Field, I^.KeyField);                    (* Return key expresison    *)
  1297. END KeyField;
  1298.  
  1299. PROCEDURE CurKey(I : NDXFile; VAR Field : ARRAY OF CHAR);
  1300. BEGIN
  1301.    ErrCode := 0;                                (* Set result code to OK    *)
  1302.    Copy(Field, I^.Key.Key);
  1303. END CurKey;
  1304.  
  1305. BEGIN
  1306.    SetRetries(0,0);                             (* Turn off auto retries    *)
  1307.    MultiUser   := FALSE;                        (* Default is Single user   *)
  1308.    Safety      := FALSE;                        (* Don't flush bufs on write*)
  1309.    ErrCheck    := AskUser;                      (* Stop & Report on Errors  *)
  1310.    ErrCode     := 0;                            (* Init error return code   *)
  1311.    DosCode     := 0;                            (* Init DOS error code      *)
  1312. END NDX.