home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 7 / Apprentice-Release7.iso / Source Code / Pascal / Snippets / PNL Libraries / MyDatabase.p < prev    next >
Encoding:
Text File  |  1997-04-13  |  22.8 KB  |  811 lines  |  [TEXT/CWIE]

  1. unit MyDatabase;
  2.  
  3. interface
  4.  
  5.     uses
  6.         Types, Files;
  7.  
  8.     const
  9.         DB_Normal = 0;
  10.         DB_CaseSensitive = $00000001;
  11.         DB_Null = 0;
  12.  
  13.     const
  14.         fileFormatErr = -10;
  15.         duplicateKeyErr = -11;
  16.         keyNotFoundErr = -12;
  17.  
  18.     function DatabaseCreate (var fs: FSSpec; hashsize: integer; flags: longint): OSErr;
  19. { You should create the file before calling this using FSpCreate.  Any existing data will be destroyed. }
  20. { hashsize is the number of hash table entries (initial file size will be around 4*hashsize }
  21. { hashsize should be prime }
  22.     function DatabaseOpen (var fs: FSSpec; var refnum: longint): OSErr;
  23.     function DatabaseFlush (refnum: longint): OSErr;
  24.     function DatabaseClose (refnum: longint): OSErr;
  25.     function DatabaseAdd (refnum: longint; key: Str255; data: Handle; overwriteok: boolean): OSErr;
  26.     function DatabaseSetInfo (refnum: longint; key: Str255; var id: longint; size: longint; overwriteok: boolean): OSErr;
  27.     function DatabaseSetChunk (refnum: longint; id: longint; pos: longint; data: Handle): OSErr;
  28.     function DatabaseGet (refnum: longint; key: Str255; data: Handle): OSErr; { data may be nil }
  29.     function DatabaseGetInfo (refnum: longint; key: Str255; var id: longint; var size: longint): OSErr;
  30.     function DatabaseGetChunk (refnum: longint; id: longint; pos, len: longint; data: Handle): OSErr;
  31.     function DatabaseDelete (refnum: longint; key: Str255; data: Handle): OSErr; { data may be nil }
  32.     function DatabaseIndex (refnum: longint; var pos: longint; var key: Str255; data: Handle): OSErr;
  33. { pass in zero the first time, then whatever you got last time to get next.   data may be nil }
  34.     function DatabasePack (refnum: longint; fix: boolean): OSErr;
  35. { uses about hashsize*8+8k memory in the heap }
  36.     function DatabaseValidate ( refnum: longint; fix_minor_errors: boolean; var minor_errors: boolean ): OSErr;
  37.  
  38. implementation
  39.  
  40.     uses
  41.         Memory, Packages, TextUtils, 
  42.         MyFileSystemUtils, MyMemory, MyAssertions;
  43.  
  44.     const
  45.         File_Magic = 'PLDB';
  46.         Current_Version = 1;
  47.         Max_Hash = 30011;
  48.         free_next = -1;
  49.  
  50. { File format: }
  51. { magic:longint }
  52. { version: longint }
  53. { flags:longint }
  54. { hashsize: integer}
  55. { hashtable: array[1..hashsize] of entryptr (offset into file) }
  56. { entry is: }
  57. { next:entryptr }
  58. { keylen:integer }
  59. { datalen:longint }
  60. { key:bytes }
  61. { data:bytes }
  62. { free entries have next=-1.  next links always point further into the file, never backwards }
  63.  
  64. {$PUSH}
  65. {$ALIGN MAC68K}
  66.  
  67.     type
  68.         ShortFileHeader = record
  69.                 magic: OSType;
  70.                 version: longint;
  71.                 flags: longint;
  72.                 hashsize: integer;
  73.                 rn: integer; { not valid in file obviously }
  74.             end;
  75.         HashTableArray = array[0..Max_Hash] of longint;
  76.         LongFileHeader = record
  77.                 magic: OSType;
  78.                 version: longint;
  79.                 flags: longint;
  80.                 hashsize: integer;
  81.                 rn: integer; { not valid in file obviously }
  82.                 hashtable: HashTableArray;
  83.             end;
  84.         FileHeaderPtr = ^LongFileHeader;
  85.         FileHeaderHandle = ^FileHeaderPtr;
  86.         HashTablePtr = ^HashTableArray;
  87.         EntryRecord = record
  88.                 next: longint;
  89.                 keylen: integer;
  90.                 datalen: longint;
  91.             end;
  92.  
  93. {$ALIGN RESET}
  94. {$POP}
  95.  
  96.     const
  97.         File_Header_Size = SizeOf(ShortFileHeader);
  98.         Entry_Size = SizeOf(EntryRecord);
  99.  
  100.     procedure AddOSErr( var err: OSErr; newerr: OSErr );
  101.     begin
  102.         if err = noErr then begin
  103.             err := newerr;
  104.         end;
  105.     end;
  106.     
  107.     function DatabaseCreate (var fs: FSSpec; hashsize: integer; flags: longint): OSErr;
  108.         var
  109.             err, oerr: OSErr;
  110.             fhp: FileHeaderPtr;
  111.             count: longint;
  112.             rn: integer;
  113.             i: integer;
  114.     begin
  115.         if hashsize > Max_Hash then begin
  116.             hashsize := Max_Hash;
  117.         end;
  118.         count := File_Header_Size + 4 * longint(hashsize);
  119.         err := FSpOpenDF(fs, fsRdWrPerm, rn);
  120.         if err = noErr then begin
  121.             err := SetEOF(rn, count);
  122.             if err = noErr then
  123.                 err := MNewPtr(fhp, count);
  124.             if err = noErr then begin
  125.                 fhp^.magic := File_Magic;
  126.                 fhp^.version := Current_Version;
  127.                 fhp^.flags := flags;
  128.                 fhp^.hashsize := hashsize;
  129.                 for i := 0 to hashsize - 1 do begin
  130.                     fhp^.hashtable[i] := 0;
  131.                 end;
  132.                 err := FSWrite(rn, count, Ptr(fhp));
  133.                 MDisposePtr(fhp);
  134.             end;
  135.             oerr := FSClose(rn);
  136.             if err = noErr then
  137.                 err := oerr;
  138.         end;
  139.         DatabaseCreate := err;
  140.     end;
  141.  
  142.     function DatabaseOpen (var fs: FSSpec; var refnum: longint): OSErr;
  143.         var
  144.             err, junk: OSErr;
  145.             fh: ShortFileHeader;
  146.             rn: integer;
  147.             count: longint;
  148.             fhp: FileHeaderPtr;
  149.     begin
  150.         err := FSpOpenDF(fs, fsRdWrPerm, rn);
  151.         if err = noErr then begin
  152.             count := File_Header_Size;
  153.             err := FSRead(rn, count, @fh);
  154.             if err = noErr then begin
  155.                 if (fh.magic <> File_magic) or (fh.version <> Current_Version) or (fh.hashsize < 1) or (fh.hashsize > Max_Hash) then begin
  156.                     err := fileFormatErr;
  157.                 end;
  158.             end;
  159.             if err = noErr then begin
  160.                 count := 4 * longint(fh.hashsize);
  161.                 err := MNewPtr(fhp, File_header_Size + count);
  162.             end;
  163.             if err = noErr then begin
  164.                 BlockMove(@fh, Ptr(fhp), File_Header_Size);
  165.                 fhp^.rn := rn;
  166.                 err := FSRead(rn, count, Ptr(ord(fhp) + File_Header_Size));
  167.                 if err <> noErr then begin
  168.                     MDisposePtr(fhp);
  169.                 end;
  170.             end;
  171.             if err <> noErr then begin
  172.                 junk := FSClose(rn);
  173.             end;
  174.         end;
  175.         refnum := longint(fhp);
  176.         if err <> noErr then begin
  177.             refnum := DB_Null;
  178.         end;
  179.         DatabaseOpen := err;
  180.     end;
  181.  
  182.     function DatabaseFlush (refnum: longint): OSErr;
  183.         var
  184.             err: OSErr;
  185.             fhp: FileHeaderptr;
  186.             pb: ParamBlockRec;
  187.     begin
  188.         fhp := FileHeaderPtr(refnum);
  189.         err := MyFSWriteAt(fhp^.rn, fsFromStart, 0, GetPtrSize(Ptr(fhp)), Ptr(fhp));
  190.         if err = noErr then begin
  191.             pb.ioRefNum := fhp^.rn;
  192.             err := PBFlushFileSync(@pb);
  193.         end;
  194.         DatabaseFlush := err;
  195.     end;
  196.  
  197.     function DatabaseClose (refnum: longint): OSErr;
  198.         var
  199.             err, oerr: OSErr;
  200.             fhp: FileHeaderptr;
  201.     begin
  202.         fhp := FileHeaderPtr(refnum);
  203.         err := MyFSWriteAt(fhp^.rn, fsFromStart, 0, GetPtrSize(Ptr(fhp)), Ptr(fhp));
  204.         oerr := FSClose(fhp^.rn);
  205.         if err = noErr then
  206.             err := oerr;
  207.         MDisposePtr(fhp);
  208.         DatabaseClose := err;
  209.     end;
  210.  
  211.     function Hash (var key: Str255; hashsize: integer): integer;
  212.         var
  213.             h, i: integer;
  214.     begin
  215.         h := 0;
  216.         for i := 1 to length(key) do begin
  217.             h := ((32 * longint(h)) + ord(key[i])) mod hashsize;
  218.         end;
  219.         Hash := h;
  220.     end;
  221.  
  222.     function ReadEntry (fhp: FileHeaderPtr; pos: longint; var entry: EntryRecord; var key: Str255): OSErr;
  223.         var
  224.             err: OSErr;
  225.     begin
  226.         err := MyFSReadAt(fhp^.rn, pos, Entry_Size, @entry);
  227.         if err = noErr then begin
  228. {$PUSH}
  229. {$R-}
  230.             key[0] := chr(entry.keylen);
  231. {$POP}
  232.             Assert( (entry.keylen > 0) & (entry.keylen <= 255) );
  233.             if (entry.keylen > 0) & (entry.keylen <= 255) then begin
  234.                 err := MyFSReadAt(fhp^.rn, pos + Entry_Size, entry.keylen, @key[1]);
  235.             end else begin
  236.                 err := fileFormatErr;
  237.             end;
  238.         end;
  239.         ReadEntry := err;
  240.     end;
  241.  
  242.     function Find (fhp: FileHeaderPtr; var key: Str255; var h: integer; var preoffset, offset: longint; var entry: EntryRecord): OSErr;
  243. { err = noErr ==> no error.  offset<>0 ==> found. preoffset is the fileoffset that points to offset (even if not found) }
  244.         var
  245.             err: OSErr;
  246.             thiskey: Str255;
  247.     begin
  248.         h := Hash(key, fhp^.hashsize);
  249.         preoffset := File_Header_Size + 4 * longint(h);
  250.         offset := fhp^.hashtable[h];
  251.         err := noErr;
  252.         while (offset <> 0) & (err = noErr) do begin
  253.             err := ReadEntry(fhp, offset, entry, thiskey);
  254.             if err = noErr then begin
  255.                 if BAND(fhp^.flags, DB_CaseSensitive) <> 0 then begin
  256.                     if thiskey = key then begin
  257.                         leave;
  258.                     end;
  259.                 end else begin
  260.                     if IUEqualString(thiskey, key) = 0 then begin
  261.                         leave;
  262.                     end;
  263.                 end;
  264.                 preoffset := offset;
  265.                 offset := entry.next;
  266.             end;
  267.         end;
  268.         Find := err;
  269.     end;
  270.  
  271.     function WriteLink (fhp: FileHeaderPtr; pos: longint; link: longint): OSErr;
  272.         var
  273.             h: integer;
  274.             err: OSErr;
  275.     begin
  276.         if pos >= File_Header_Size + 4 * longint(fhp^.hashsize) then begin
  277.             err := MyFSWriteAt(fhp^.rn, fsFromStart, pos, 4, @link);
  278.         end else begin
  279.             err := noErr;
  280.             h := (pos - File_Header_size) div 4;
  281.             fhp^.hashtable[h] := link;
  282.         end;
  283.         WriteLink := err;
  284.     end;
  285.  
  286.     function WriteFreeLink (fhp: FileHeaderPtr; pos: longint): OSErr;
  287.         var
  288.             link: longint;
  289.     begin
  290.         link := free_next;
  291.         WriteFreeLink := MyFSWriteAt(fhp^.rn, fsFromStart, pos, 4, @link);
  292.     end;
  293.  
  294.     function FindSpace (fhp: FileHeaderptr; key: Str255; size: longint; overwriteok: boolean; var offset: longint): OSErr;
  295.         var
  296.             err: OSErr;
  297.             h: integer;
  298.             preoffset, v: longint;
  299.             entry: EntryRecord;
  300.             filelen: longint;
  301.             oldsize: longint;
  302.     begin
  303.         err := Find(fhp, key, h, preoffset, offset, entry);
  304.         if (err = noErr) & (offset <> 0) & not overwriteok then
  305.             err := duplicateKeyErr;
  306.         if (err = noErr) & (offset <> 0) then begin
  307.             if entry.datalen = size then begin
  308. { all set }
  309.             end else if entry.datalen > size + Entry_Size then begin
  310.                 oldsize := entry.datalen;
  311.                 entry.datalen := size;
  312.                 err := MyFSWriteAt(fhp^.rn, fsFromStart, offset, Entry_Size, @entry);
  313.                 if err = noErr then begin
  314.                     entry.next := free_next;
  315.                     entry.keylen := 0;
  316.                     entry.datalen := oldsize - size - Entry_Size;
  317.                     err := MyFSWriteAt(fhp^.rn, fsFromStart, offset + Entry_Size + length(key) + size, Entry_Size, @entry);
  318.                 end;
  319.             end else begin
  320.                 err := WriteLink(fhp, preoffset, entry.next);
  321.                 v := free_next;
  322.                 if err = noErr then
  323.                     err := WriteFreeLink(fhp, offset);
  324.                 offset := entry.next;
  325.                 while (offset <> 0) & (err = noErr) do begin
  326.                     err := MyFSReadAt(fhp^.rn, offset, 4, @entry);
  327.                     if err = noErr then begin
  328.                         preoffset := offset;
  329.                         offset := entry.next;
  330.                     end;
  331.                 end;
  332.             end;
  333.         end;
  334.         if (err = noErr) & (offset = 0) then begin { add at end of file after entry at preoffset }
  335.             err := GetEOF(fhp^.rn, filelen);
  336.             if err = noErr then begin
  337.                 err := SetEOF(fhp^.rn, filelen + Entry_Size + length(key) + size);
  338.             end;
  339.             entry.next := 0;
  340.             entry.keylen := length(key);
  341.             entry.datalen := size;
  342.             if err = noErr then
  343.                 err := MyFSWriteAt(fhp^.rn, fsFromStart, filelen, Entry_Size, @entry);
  344.             if err = noErr then
  345.                 err := MyFSWrite(fhp^.rn, length(key), @key[1]);
  346.             if err = noErr then begin
  347.                 err := WriteLink(fhp, preoffset, filelen);
  348.             end;
  349.             offset := filelen;
  350.         end;
  351.         offset := offset + Entry_Size + length(key);
  352.         FindSpace := err;
  353.     end;
  354.  
  355.     function DatabaseAdd (refnum: longint; key: Str255; data: Handle; overwriteok: boolean): OSErr;
  356.         var
  357.             err: OSErr;
  358.             fhp: FileHeaderptr;
  359.             offset: longint;
  360.             handlesize: longint;
  361.             state: SignedByte;
  362.     begin
  363.         fhp := FileHeaderPtr(refnum);
  364.         handlesize := GetHandleSize(data);
  365.         err := FindSpace(fhp, key, handlesize, overwriteok, offset);
  366.         if err = noErr then begin
  367.             HLockState(data, state);
  368.             err := MyFSWriteAt(fhp^.rn, fsFromStart, offset, handlesize, data^);
  369.             HSetState(data, state);
  370.         end;
  371.         DatabaseAdd := err;
  372.     end;
  373.  
  374.     function DatabaseSetInfo (refnum: longint; key: Str255; var id: longint; size: longint; overwriteok: boolean): OSErr;
  375.         var
  376.             err: OSErr;
  377.             fhp: FileHeaderptr;
  378.     begin
  379.         fhp := FileHeaderPtr(refnum);
  380.         err := FindSpace(fhp, key, size, overwriteok, id);
  381.         DatabaseSetInfo := err;
  382.     end;
  383.  
  384.     function DatabaseSetChunk (refnum: longint; id: longint; pos: longint; data: Handle): OSErr;
  385.         var
  386.             err: OSErr;
  387.             fhp: FileHeaderptr;
  388.             state: SignedByte;
  389.     begin
  390.         fhp := FileHeaderPtr(refnum);
  391.         HLockState(data, state);
  392.         err := MyFSWriteAt(fhp^.rn, fsFromStart, id + pos, GetHandleSize(data), data^);
  393.         HSetState(data, state);
  394.         DatabaseSetChunk := err;
  395.     end;
  396.  
  397.     function Get (fhp: FileHeaderPtr; var key: Str255; var h: integer; var preoffset, offset: longint; var entry: EntryRecord; data: Handle): OSErr;
  398.         var
  399.             err: OSErr;
  400.             state: SignedByte;
  401.     begin
  402.         err := Find(fhp, key, h, preoffset, offset, entry);
  403.         if (err = noErr) & (offset = 0) then
  404.             err := keyNotFoundErr;
  405.         if err = noErr then begin
  406.             if data <> nil then begin
  407.                 HUnlockState(data, state);
  408.                 SetHandleSize(data, entry.datalen);
  409.                 err := MemError;
  410.                 if err = noErr then begin
  411.                     HLock(data);
  412.                     err := MyFSReadAt(fhp^.rn, offset + Entry_Size + entry.keylen, entry.datalen, data^);
  413.                 end;
  414.                 HSetState(data, state);
  415.             end;
  416.         end;
  417.         Get := err;
  418.     end;
  419.  
  420.     function DatabaseGet (refnum: longint; key: Str255; data: Handle): OSErr;
  421.         var
  422.             h: integer;
  423.             preoffset, offset: longint;
  424.             entry: EntryRecord;
  425.     begin
  426.         DatabaseGet := Get(FileHeaderPtr(refnum), key, h, preoffset, offset, entry, data);
  427.     end;
  428.  
  429.     function DatabaseGetInfo (refnum: longint; key: Str255; var id: longint; var size: longint): OSErr;
  430.         var
  431.             h: integer;
  432.             preoffset, offset: longint;
  433.             entry: EntryRecord;
  434.     begin
  435.         DatabaseGetInfo := Get(FileHeaderPtr(refnum), key, h, preoffset, offset, entry, nil);
  436.         id := offset + Entry_Size + entry.keylen;
  437.         size := entry.datalen;
  438.     end;
  439.  
  440.     function DatabaseGetChunk (refnum: longint; id: longint; pos, len: longint; data: Handle): OSErr;
  441.         var
  442.             err: OSErr;
  443.             state: SignedByte;
  444.     begin
  445.         HUnlockState(data, state);
  446.         SetHandleSize(data, len);
  447.         err := MemError;
  448.         if err = noErr then begin
  449.             HLock(data);
  450.             err := MyFSReadAt(FileHeaderPtr(refnum)^.rn, id + pos, len, data^);
  451.         end;
  452.         HSetState(data, state);
  453.         DatabaseGetChunk := err; { Thanks Marcel/Metrowerks! }
  454.     end;
  455.  
  456.     function DatabaseDelete (refnum: longint; key: Str255; data: Handle): OSErr; { data may be nil }
  457.         var
  458.             err: OSErr;
  459.             fhp: FileHeaderptr;
  460.             h: integer;
  461.             preoffset, offset: longint;
  462.             entry: EntryRecord;
  463.     begin
  464.         fhp := FileHeaderPtr(refnum);
  465.         err := Get(fhp, key, h, preoffset, offset, entry, data);
  466.         if err = noErr then begin
  467.             err := WriteLink(fhp, preoffset, entry.next);
  468.             if err = noErr then
  469.                 err := WriteFreeLink(fhp, offset);
  470.         end;
  471.         DatabaseDelete := err;
  472.     end;
  473.  
  474.     function DatabaseIndex (refnum: longint; var pos: longint; var key: Str255; data: Handle): OSErr;
  475.         var
  476.             err: OSErr;
  477.             fhp: FileHeaderptr;
  478.             start, filelen: longint;
  479.             entry: EntryRecord;
  480.             count: longint;
  481.     begin
  482.         fhp := FileHeaderPtr(refnum);
  483.         start := File_Header_Size + 4 * longint(fhp^.hashsize);
  484.         if pos = 0 then
  485.             pos := start;
  486.         err := GetEOF(fhp^.rn, filelen);
  487.         entry.next := free_next;
  488.         while (err = noErr) & (entry.next = free_next) & (start <= pos) & (pos < filelen) do begin
  489.             err := ReadEntry(fhp, pos, entry, key);
  490.             pos := pos + Entry_Size + entry.keylen + entry.datalen;
  491.         end;
  492.         if (err = noErr) & (entry.next = free_next) then
  493.             err := keyNotFoundErr;
  494.         if (err = noErr) & (data <> nil) then begin
  495.             SetHandleSize(data, entry.datalen);
  496.             err := MemError;
  497.             if err = noErr then begin
  498.                 count := entry.datalen;
  499.                 err := FSRead(fhp^.rn, count, data^);
  500.             end;
  501.         end;
  502.         DatabaseIndex := err;
  503.     end;
  504.  
  505.     function DatabasePack (refnum: longint; fix: boolean): OSErr;
  506.         const
  507.             buffer_size = 8192;
  508.         var
  509.             err: OSErr;
  510.             fhp: FileHeaderptr;
  511.             preoffsets, offsets: HashTablePtr;
  512.             start, filelen: longint;
  513.             srcpos, destpos, nextpos: longint;
  514.             entry: EntryRecord;
  515.             key: Str255;
  516.             len, count: longint;
  517.             buffer: Ptr;
  518.             h: integer;
  519.     begin
  520.         fhp := FileHeaderPtr(refnum);
  521.         err := MNewPtr(preoffsets, 4 * longint(fhp^.hashsize));
  522.         offsets := nil;
  523.         if err = noErr then
  524.             err := MNewPtr(offsets, 4 * longint(fhp^.hashsize));
  525.         buffer := nil;
  526.         if err = noErr then
  527.             err := MNewPtr(buffer, buffer_size);
  528.         start := File_Header_Size + 4 * longint(fhp^.hashsize);
  529.         if err = noErr then
  530.             err := GetEOF(fhp^.rn, filelen);
  531.         if err = noErr then begin
  532.             for h := 0 to fhp^.hashsize - 1 do begin
  533.                 preoffsets^[h] := File_header_Size + longint(h) * 4;
  534.                 offsets^[h] := fhp^.hashtable[h];
  535.                 if fix then begin
  536.                     fhp^.hashtable[h] := 0;
  537.                 end;
  538.             end;
  539.             srcpos := start;
  540.             destpos := start;
  541.             while (err = noErr) & (srcpos < filelen) do begin
  542.                 err := ReadEntry(fhp, srcpos, entry, key);
  543.                 
  544.                 if fix & (err = fileFormatErr) then begin
  545.                     nextpos := filelen;
  546.                     for h := 0 to fhp^.hashsize - 1 do begin
  547.                         if (offsets^[h] > srcpos) & (offsets^[h] < nextpos) then begin
  548.                             nextpos := offsets^[h];
  549.                         end;
  550.                     end;
  551.                     if nextpos < filelen then begin
  552.                         DebugStr( 'Skipping Entry;g' );
  553.                         srcpos := nextpos;
  554.                         err := noErr;
  555.                         cycle;
  556.                     end else begin
  557.                         err := noErr;
  558.                         leave;
  559.                     end;
  560.                 end;
  561.                 
  562.                 if (err = noErr) then begin
  563.                     len := Entry_Size + entry.keylen + entry.datalen;
  564.                     if (entry.next = free_next) then begin { skip it }
  565.                         srcpos := srcpos + len;
  566.                     end else begin
  567. { ok, now we need to move this entry from srcpos to destpos, updating the link pointing to it }
  568. { Find hash }
  569.                         h := Hash(key, fhp^.hashsize);
  570.                         if (offsets^[h] <> srcpos) & not fix then begin
  571.                             err := fileFormatErr;
  572.                         end;
  573. { Update link }
  574.                         if err = noErr then begin
  575.                             err := WriteLink(fhp, preoffsets^[h], destpos);
  576.                         end;
  577.                         preoffsets^[h] := destpos;
  578.                         offsets^[h] := entry.next;
  579. { Copy entry }
  580.                         if srcpos = destpos then begin
  581.                             destpos := destpos + len;
  582.                             srcpos := srcpos + len;
  583.                         end else begin
  584.                             while (err = noErr) & (len > 0) do begin
  585.                                 count := len;
  586.                                 if count > buffer_size then begin
  587.                                     count := buffer_size;
  588.                                 end;
  589.                                 err := MyFSReadAt(fhp^.rn, srcpos, count, buffer);
  590.                                 if err = noErr then begin
  591.                                     err := MyFSWriteAt(fhp^.rn, fsFromStart, destpos, count, buffer);
  592.                                 end;
  593.                                 len := len - count;
  594.                                 srcpos := srcpos + count;
  595.                                 destpos := destpos + count;
  596.                             end;
  597.                         end;
  598.                     end;
  599.                 end;
  600.             end;
  601.             if err = noErr then begin
  602.                 { terminate all chains }
  603.                 for h := 0 to fhp^.hashsize - 1 do begin
  604.                     AddOSErr( err, WriteLink( fhp, preoffsets^[h], 0 ) );
  605.                 end;
  606.                 AddOSErr( err, SetEOF(fhp^.rn, destpos) );
  607.             end;
  608.         end;
  609.         MDisposePtr(preoffsets);
  610.         MDisposePtr(offsets);
  611.         MDisposePtr(buffer);
  612.         DatabasePack := err;
  613.     end;
  614.  
  615.     function DatabaseValidate ( refnum: longint; fix_minor_errors: boolean; var minor_errors: boolean ): OSErr;
  616.         var
  617.             current: ^HashTableArray;
  618.             fhp: FileHeaderptr;
  619.             pos: longint;
  620.             all_finished: boolean;
  621.         function FindInCurrent( pos: longint ): longint;
  622.             var
  623.                 i, j: longint;
  624.         begin
  625.             j := -1;
  626.             for i := 0 to fhp^.hashsize-1 do begin
  627.                 if current^[i] = pos then begin
  628.                     if j >= 0 then begin
  629.                         minor_errors := true;
  630.                         writeln( 'Duplicate hash entry pointing at ', pos:1, ' ', i:1, ' ', j:1 );
  631.                     end;
  632.                     j := i;
  633.                 end;
  634.             end;
  635.             FindInCurrent := j;
  636.         end;
  637.         function FindCurrent: longint;
  638.         begin
  639.             FindCurrent := FindInCurrent( pos );
  640.         end;
  641.         function FindNext: longint;
  642.             var
  643.                 i, j: longint;
  644.         begin
  645.             j := -1;
  646.             for i := 0 to fhp^.hashsize-1 do begin
  647.                 if current^[i] > pos then begin
  648.                     if (j < 0) | (current^[i] < current^[j]) then begin
  649.                         j := i;
  650.                     end;
  651.                 end;
  652.             end;
  653.             FindNext := j;
  654.         end;
  655.         
  656.         procedure SetAllFinished;
  657.             var
  658.                 i: longint;
  659.         begin
  660.             all_finished := true;
  661.             for i := 0 to fhp^.hashsize-1 do begin
  662.                 if current^[i] >= pos then begin
  663.                     all_finished := false;
  664.                 end;
  665.             end;
  666.         end;
  667.         
  668.         procedure SetCurrent( i, what: longint );
  669.             var
  670.                 junk: OSErr;
  671.         begin
  672.             if (what > 0) & (FindInCurrent( what ) >= 0) then begin
  673.                 minor_errors := true;
  674.                 writeln( 'Ignoring duplicate SetCurrent at ', pos:1, ' ', i:1, ' ', FindInCurrent( what ):1, ' ', what:1 );
  675.                 if fix_minor_errors then begin
  676.                     junk := WriteLink( fhp, pos, 0 ); { termiate chain }
  677.                 end;
  678.                 what := 0;
  679.             end;
  680.             current^[i] := what;
  681.             if what <= 0 then begin
  682.                 SetAllFinished;
  683.             end;
  684.         end;
  685.         var
  686.             err: OSErr;
  687.             filelen: longint;
  688.             hash_size: longint;
  689.             nextpos: longint;
  690.             entry: EntryRecord;
  691.             i, j: longint;
  692.             key: Str255;
  693.             junk: OSErr;
  694.     begin
  695.         minor_errors := false;
  696.         current := nil;
  697.         fhp := FileHeaderPtr(refnum);
  698.         err := GetEOF( fhp^.rn, filelen );
  699.         if err = noErr then begin
  700.             Assert( filelen >= File_Header_Size );
  701.             hash_size := 4 * longint(fhp^.hashsize);
  702.             err := MNewPtr( current, hash_size);
  703.         end;
  704.         if err = noErr then begin
  705.             BlockMoveData( @fhp^.hashtable, Ptr(current), hash_size );
  706.             pos := File_Header_Size + hash_size;
  707.             SetAllFinished;
  708.             while not all_finished & (pos < filelen) do begin
  709.                 err := MyFSReadAt( fhp^.rn, pos, Entry_Size, @entry );
  710.                 if err <> noErr then begin
  711.                     writeln( 'DatabaseValidate:MyFSReadAt ', err:1, ' at ', pos:1 );
  712.                     leave;
  713.                 end;
  714.                 if (entry.keylen <= 0) | (entry.keylen > 255) | (entry.datalen < 0) | (entry.datalen > filelen - pos) then begin
  715.                     j := FindCurrent;
  716.                     i := FindNext;
  717.                     if j > 0 then begin
  718.                         SetCurrent( i, -1 );
  719.                     end;
  720.                     if i > 0 then begin
  721.                         nextpos := current^[i];
  722.                     end else begin
  723.                         nextpos := filelen;
  724.                     end;
  725.                     if fix_minor_errors then begin
  726.                         { blat pos->nextpos }
  727.                         key := 'A';
  728.                         if j < 0 then begin
  729.                             entry.next := free_next;
  730.                         end else begin
  731.                             entry.next := 0;
  732.                             if Hash( key, fhp^.hashsize ) = j then begin { ensure key does not match hash }
  733.                                 key := 'B';
  734.                             end;
  735.                         end;
  736.                         entry.keylen := 1;
  737.                         entry.datalen := nextpos - pos - Entry_Size - entry.keylen;
  738.                         junk := MyFSWriteAt( fhp^.rn, fsFromStart, pos, Entry_Size, @entry  );
  739.                         junk := MyFSWriteAt( fhp^.rn, fsFromStart, pos + Entry_Size, 1, @key[1] );
  740.                     end;
  741.                     minor_errors := true;
  742.                     writeln( 'DatabaseValidate:Entry lengths invalid ', pos:1, ' ', i:1, ' ', j:1 );
  743.                     pos := nextpos;
  744. {                    err := fileFormatErr; }
  745.                 end else if entry.next = -1 then begin
  746.                     { do nothing }
  747.                     pos := pos + Entry_Size + entry.keylen + entry.datalen;
  748.                 end else begin
  749.                     j := FindCurrent;
  750.                     if j < 0 then begin
  751.                         key[0] := chr(entry.keylen);
  752.                         err := MyFSReadAt( fhp^.rn, pos + Entry_Size, entry.keylen, @key[1] );
  753.                         if err <> noErr then begin
  754.                             writeln( 'DatabaseValidate:Search failed, key read failed at ', pos:1, ' ', err:1 );
  755.                             err := fileFormatErr;
  756.                             leave;
  757.                         end;
  758.                         j := Hash( key, fhp^.hashsize );
  759.                         if current^[j] < 0 then begin
  760.                             minor_errors := true;
  761.                             writeln( 'DatabaseValidate:Search failed, relinking at ', pos:1, ' ', j:1 );
  762.                             SetCurrent( j, entry.next );
  763.                         end else if current^[j] = 0 then begin
  764.                             minor_errors := true;
  765.                             writeln( 'DatabaseValidate:Search failed, ignoring at ', pos:1, ' ', j:1 );
  766.                             if fix_minor_errors then begin
  767.                                 junk := WriteFreeLink( fhp, pos ); { free entry }
  768.                             end;
  769.                         end else begin
  770.                             writeln( 'DatabaseValidate:Search failed at ', pos:1, ' ', j:1 );
  771.                             if fix_minor_errors then begin
  772.                                 junk := WriteFreeLink( fhp, pos ); { free entry }
  773.                             end;
  774.                         end;
  775.                     end else begin
  776.                         SetCurrent( j, entry.next );
  777.                     end;
  778.                     pos := pos + Entry_Size + entry.keylen + entry.datalen;
  779.                 end;
  780.             end;
  781.             if (err = noErr) & (pos <> filelen) then begin
  782.                 if all_finished then begin
  783.                     writeln( 'DatabaseValidate:Did not end at end  of file, fixed ', pos:1 );
  784.                     if fix_minor_errors then begin
  785.                         junk := SetEOF( fhp^.rn, pos );
  786.                     end;
  787.                 end else begin
  788.                     err := fileFormatErr;
  789.                     writeln( 'DatabaseValidate:Did not end at end  of file' );
  790.                 end;
  791.             end;
  792.             if (err = noErr) then begin
  793.                 for i := 0 to fhp^.hashsize-1 do begin
  794.                     if current^[i] > 0 then begin
  795.                         err := fileFormatErr;
  796.                         writeln( 'DatabaseValidate:Did not use all of hashtable entry ', i:1, ' ', current^[i] );
  797.                     end;
  798.                 end;
  799.             end;
  800.         end;
  801.         MDisposePtr( current );
  802.         DatabaseValidate := err;
  803.     end;
  804.     
  805. end.
  806.  
  807. { Edit history }
  808. {11 Dec 95    pnl        Original }
  809. { 5 May 96    jc            Changes to support large DBs – again, since I lost the original edits }
  810. { 21 Aug 96    pnl        Added 68k alignment directives, fixed case, merged back in to main source }
  811.