{$IFDEF WtrGate}{$IFDEF UseOvr}{$O+,F+}{$ENDIF}{$ENDIF}
UNIT DupeChk;

{$i platform.inc}

{ History:

RvdW 26-02-93 Deze unit gemaakt.
     27-02-93 DupeCheckExist routine met FOR loop is veeeel te traag.
              Omgezet in assembly.
     980106   Rewrite, new way of storing dupes, sections, per area.
}

{## idea to optimize: keep track of last n stored AreaRef,DupeValue to
                      quickly check for dupes in same section}

{ $DEFINE DebugDupeCheck} { enables a lot of messages }

INTERFACE

USES Database;

PROCEDURE DupeCheckInit;
PROCEDURE DupeCheckEnd;

FUNCTION  DupeCheck_CalcDupeValue_Echomail : LONGINT;
FUNCTION  DupeCheck_CalcDupeValue_News: LONGINT;

PROCEDURE DupeCheck_SetAreaRef (AreaRecNr : AreaBaseRecordNrType; VAR AreaRec : AreaBaseRecord);
FUNCTION  DupeCheck_Exists (DupeValue : LONGINT) : BOOLEAN;

FUNCTION  DupeCheck_PackDupeBase : BYTE;


IMPLEMENTATION

USES Ramon,
     Msgs,
     Cfg,
     Logs,
     UnixTime,
     Globals;

CONST DUPE_HEADER = 'WaterGate Dupe Database v01'+#26; { length = 28 }

CONST MAX_VALUES_PER_SECTION = 1024;

TYPE DupeSectionRecord = RECORD
                               Length  : WORD;    { including header }
                               NextPos : LONGINT; { next section }
                               AreaRef : LONGINT; { for check/recovery }
                               Sum     : WORD;    { sum in all sections }
                                                  { prev for AreaRef + this }
                               Created : LONGINT; { unix stamp, for stats }
                               Age     : LONGINT; { unix stamp, when full }
                               Count   : WORD;    { used in this block }

                               { variable length, following is maximum }
                               Values  : ARRAY[1..MAX_VALUES_PER_SECTION] OF LONGINT;
                         END;

CONST SECTION_REC_STATIC = SizeOf (DupeSectionRecord)-MAX_VALUES_PER_SECTION*4;

VAR DupeIsOpen    : BOOLEAN;
    DupeFile      : FILE;
    AreaRef       : LONGINT;
    AreaDoCheck   : BOOLEAN;
    AreaDupeFirst : LONGINT;
    SectionRec    : DupeSectionRecord;  { about 4Kb static memory }

{--------------------------------------------------------------------------}
{ DupeCheckInit                                                            }
{                                                                          }
{ This routine initialises this unit. The dupe database is opened or       }
{ created if not already present.                                          }
{                                                                          }
PROCEDURE DupeCheckInit;

VAR IORes    : BYTE;
    Filename : STRING;
    Header   : STRING[28];

BEGIN
     DupeIsOpen:=FALSE;

     IF (NOT Config.DoDupeChk) THEN
        Exit;

     Filename:=Config.SystemDir+'DUPEBASE.TDB';

     Assign (DupeFile,Filename);
     {$I-} Reset (DupeFile,1); {$I+} IORes:=IOResult;
{$IFNDEF WtrUtil}
     IF (IORes = 2) THEN
     BEGIN
          {$I-} ReWrite (DupeFile,1); {$I+} IORes:=IOResult;
          IF (IORes <> 0) THEN
          BEGIN
               LogDiskIOError (IORes,'Error creating dupe database '+Filename);
               Exit;
          END;

          { write the header }
          Header:=DUPE_HEADER;
          BlockWrite (DupeFile,Header[1],28);

     END ELSE
{$ENDIF}
         IF (IORes <> 0) THEN
         BEGIN
              LogDiskIOError (IORes,'Error opening dupe database '+Filename);
              Exit;
         END;

     {$IFDEF LogFileIO}PostOpenF (DupeFile);{$ENDIF}

     { check the header }
     Seek (DupeFile,0); { might have just written the header }
     BlockRead (DupeFile,Header[1],28);
     Header[0]:=#28;

     IF (Header <> DUPE_HEADER) THEN
     BEGIN
          LogMessage (liFatal,'Wrong header in Dupe Database; not used!!');
          {$IFDEF LogFileIO}PreCloseF (DupeFile);{$ENDIF}
          Close (DupeFile);
          Config.DoDupeChk:=FALSE;
          Exit;
     END;

     {$IFNDEF WtrUtil}
     IF Config.LogDebug THEN
        LogMessage (liTrivial,'Dupe Database is ready');
     {$ENDIF}

     { everything cool }
     DupeIsOpen:=TRUE;
END;


{--------------------------------------------------------------------------}
{ DupeCheckEnd                                                             }
{                                                                          }
{ This routine must be called before the program exists to clean up this   }
{ module.                                                                  }
{                                                                          }
PROCEDURE DupeCheckEnd;
BEGIN
     IF DupeIsOpen THEN
     BEGIN
          {$IFNDEF WtrUtil}
          IF Config.LogDebug THEN
             LogMessage (liTrivial,'Closed Dupe Database (now '+Longint2String (FileSize (DupeFile) DIV 1024)+'Kb)');
          {$ENDIF}

          {$IFDEF LogFileIO}PreCloseF (DupeFile);{$ENDIF}
          Close (DupeFile);
          DupeIsOpen:=FALSE; { RAWI 990312 }
          Exit;
     END;
END;


{--------------------------------------------------------------------------}
{ InitSectionRec                                                           }
{                                                                          }
{ This routine initializes the section rec so it is empty and ready for    }
{ use with the current AreaRef (global in this unit).                      }
{                                                                          }
PROCEDURE InitSectionRec (PrevSum : WORD);

VAR NewSize : WORD;

BEGIN
     FillChar (SectionRec,SizeOf (SectionRec),0);

     NewSize:=PrevSum DIV 2;
     IF (NewSize > MAX_VALUES_PER_SECTION) THEN
        NewSize:=MAX_VALUES_PER_SECTION;

     IF (NewSize < 16) THEN
        NewSize:=16;

     {$IFDEF DebugDupeCheck}
     LogMessage ('New Dupe Section for AreaRef $'+Long2HexString (AreaRef)+
                 ' ('+Word2String (PrevSum)+' stored) can hold '+Word2String (NewSize)+' entries');
     {$ENDIF}

     SectionRec.Length:=SECTION_REC_STATIC+NewSize*4;
     SectionRec.Sum:=PrevSum;
     SectionRec.AreaRef:=AreaRef;
     SectionRec.Created:=GetCurrentUnixTime;
END;


{--------------------------------------------------------------------------}
{ CalcAreaRef                                                              }
{                                                                          }
{ This routine calculated the AreaRef for this area and stores it in the   }
{ global variable AreaRef.                                                 }
{                                                                          }
PROCEDURE CalcAreaRef (VAR AreaRec : AreaBaseRecord);

VAR Help : STRING;

BEGIN
     Help:=AddUpWithSpaces (MaxLenAreaName,UpCaseString (AreaRec.AreaName_F))+
           AddUpWithSpaces (MaxLenAreaName,UpCaseString (AreaRec.AreaName_U));
     AreaRef:=UpdateCRC32 (0,Help[1],Length (Help));
END;


{--------------------------------------------------------------------------}
{ DupeCheck_CalcAreaRef                                                    }
{                                                                          }
{ This function is used to calculate the area reference, which is a unique }
{ identifier for this area, not based on the record nr.                    }
{ This routine also makes sure that there is a dupe block present for this }
{ area. If not, it is created at this point.                               }
{                                                                          }
PROCEDURE DupeCheck_SetAreaRef (AreaRecNr : AreaBaseRecordNrType; VAR AreaRec : AreaBaseRecord);

VAR IORes     : BYTE;
    BytesRead : WordLong;

BEGIN
     IF (NOT DupeIsOpen) THEN
        Exit;

     { if the weeks are set to 0, then do not check dupes for this area }
     AreaDoCheck:=(AreaRec.DupeAge <> 0);

     IF (NOT AreaDoCheck) THEN
        Exit;

     CalcAreaRef (AreaRec);

     IF (AreaRec.DupeList <> 0) AND
        ((AreaRec.DupeList < 28) OR (AreaRec.DupeList+SECTION_REC_STATIC > FileSize (DupeFile))) THEN
     BEGIN
          LogMessage (liFatal,'Dupe Database consistency error, resetting dupes record for area');
          LogExtraMessage (AreaRec.AreaName_F);
          AreaRec.DupeList:=0; { creates new }
     END;

     IF (AreaRec.DupeList <> 0) THEN
     BEGIN
          { read the first dupe record and check the reference }
          Seek (DupeFile,AreaRec.DupeList);
          BlockRead (DupeFile,SectionRec,SECTION_REC_STATIC,BytesRead);
          IF (SectionRec.AreaRef <> AreaRef) THEN
          BEGIN
               LogMessage (liGeneral,'Area reference has changed, resetting dupes record for area');
               LogExtraMessage (AreaRec.AreaName_F);
               AreaRec.DupeList:=0;
          END;
     END;

     IF (AreaRec.DupeList = 0) THEN
     BEGIN
          { No dupes section present for this area. Create one and write }
          { the area back to disk.                                       }

          InitSectionRec (0);

          { write the updated area record to disk }
          AreaRec.DupeList:=FileSize (DupeFile);
          WriteAreaBaseRecord (AreaRecNr,AreaRec);

          { write the new section record to the dupe database }
          Seek (DupeFile,AreaRec.DupeList);
          {$I-} BlockWrite (DupeFile,SectionRec,SectionRec.Length); {$I+} IORes:=IOResult;
          IF (IORes <> 0) THEN
             LogDiskIOError (IORes,'Error creating first Dupe Section');

          {$IFDEF DebugDupeCheck}
          LogMessage ('Created first Dupe Section at $'+Long2HexString (AreaRec.DupeList)+' for area');
          LogExtraMessage ('$'+Long2HexString (AreaRef)+' / '+AreaRec.AreaName_F);
          {$ENDIF}
     END;

     AreaDupeFirst:=AreaRec.DupeList;
END;


{--------------------------------------------------------------------------}
{ DupeCheck_CalcDupeValue_Echomail                                         }
{                                                                          }
{ This routine calculates the dupe check value over an echomail message.   }
{                                                                          }
{ Information used to calculate the DupeValue:                             }
{ - From Name (35)                                                         }
{ - From zone,net,node,point (20)                                          }
{ - To Name (35)                                                           }
{ - To zone,net,node,point (20)                                            }
{ - Subject (71)                                                           }
{ - Date (19)                                                              }
{ - Split part number from split kludge (5)                                }
{                                                                          }
{ Sum: 261 chars.                                                          }
{                                                                          }
FUNCTION DupeCheck_CalcDupeValue_Echomail : LONGINT;

VAR Help : STRING;

BEGIN
     Help:=AddUpWithSpaces (35,Msg.FromUser_F)+
           AddUpWithPre0s (5,Word2String (Msg.FromAddr_F.Zone))+
           AddUpWithPre0s (5,Word2String (Msg.FromAddr_F.Net))+
           AddUpWithPre0s (5,Word2String (Msg.FromAddr_F.Node))+
           AddUpWithPre0s (5,Word2String (Msg.FromAddr_F.Point))+
           AddUpWithSpaces (35,Msg.Stored_ToUser)+
           AddUpWithPre0s (5,Word2String (Msg.Stored_ToAddr.Zone))+
           AddUpWithPre0s (5,Word2String (Msg.Stored_ToAddr.Net))+
           AddUpWithPre0s (5,Word2String (Msg.Stored_ToAddr.Node))+
           AddUpWithPre0s (5,Word2String (Msg.Stored_ToAddr.Point))+
           AddUpWithSpaces (71,Msg.Subj_F)+
           AddUpWithSpaces (19,Msg.Date_F);

     {## add split part number}

     { add the string to a fixed length to reduce the change of CRC32  }
     { matches on none-equal length sequences. Also, make it the same  }
     { length as used for the news CRC32's to avoid matching a shorter }
     { FTN CRC32 with a RFC CRC32.                                     }
     Help:=AddUpWithSpaces (255,Help);
     DupeCheck_CalcDupeValue_Echomail:=UpdateCRC32 ($FFFFFFFF,Help[1],255);
END;


{--------------------------------------------------------------------------}
{ DupeCheck_CalcDupeValue_News                                             }
{                                                                          }
{ This routine calculates the dupe check value over an echomail message.   }
{                                                                          }
{ Information used to calculate the DupeValue:                             }
{ - Message-ID header                                                      }
{                                                                          }
FUNCTION DupeCheck_CalcDupeValue_News : LONGINT;

VAR Help : STRING;

BEGIN
     {## do not calc if AreaDoCheck is FALSE for this area?}
     { add the string to a fixed length to reduce the change of CRC32 }
     { matches on none-equal length sequences.                        }
     Help:=AddUpWithSpaces (255,Msg.MessageID_U);
     DupeCheck_CalcDupeValue_News:=UpdateCRC32 ($FFFFFFFF,Help[1],255);
END;


{--------------------------------------------------------------------------}
{ DupeCheck_Exists                                                         }
{                                                                          }
{ This routine checks whether the given dupe value already exists for this }
{ area. If so, TRUE is returns. Otherwise, the value is added and FALSE    }
{ will be returned instead.                                                }
{ AreaDupeFirst points to the first dupe block for this area.              }
{                                                                          }
FUNCTION DupeCheck_Exists (DupeValue : LONGINT) : BOOLEAN;

    PROCEDURE Corrupt (Nr : BYTE);
    BEGIN
         LogMessage (liFatal,'Dupe Database is corrupt! ('+Byte2String (Nr)+') *** Please Pack Databases ***');
         DupeCheckEnd;
    END;

VAR SectionPos : LONGINT;
    BytesRead  : WordLong;
    Lp         : WORD;
    IORes      : BYTE;

BEGIN
     DupeCheck_Exists:=FALSE; { assume not }

     IF (NOT DupeIsOpen) OR (NOT AreaDoCheck) THEN
        Exit; { with FALSE: not a dupe }

     SectionPos:=AreaDupeFirst;

     REPEAT
           IF (SectionPos < 28) OR (SectionPos > FileSize (DupeFile)) THEN
           BEGIN
                Corrupt (1);
                Exit; { not found, but not stored either }
           END;

           { read the section header from disk }
           Seek (DupeFile,SectionPos);
           BlockRead (DupeFile,SectionRec,SizeOf (DupeSectionRecord),BytesRead);
           { notice: can read less bytes at end of file }

           { check that enough bytes have been read }
           IF (BytesRead < SectionRec.Length) THEN
           BEGIN
                Corrupt (2);
                Exit;
           END;

           { check if the area reference is right }
           IF (AreaRef <> SectionRec.AreaRef) THEN
           BEGIN
                Corrupt (3);
                Exit;
           END;

           { see if we can find the dupe in this section }
           {## speed up with assembly}
           FOR Lp:=1 TO SectionRec.Count DO
               IF (SectionRec.Values[Lp] = DupeValue) THEN
               BEGIN
                    {$IFDEF DebugDupeCheck}
                    LogMessage ('Found dupe value $'+Long2HexString (DupeValue)+
                                ' at offset '+Word2String (Lp)+
                                ' in section at $'+Long2HexString (SectionPos));
                    {$ENDIF}
                    DupeCheck_Exists:=TRUE;
                    Exit;
               END;

           IF (SectionRec.NextPos = 0) THEN
           BEGIN
                { end of all sections, not found, so add it }

                IF (SectionRec.Count*4 = SectionRec.Length-SECTION_REC_STATIC) THEN
                BEGIN
                     { there is NO space left in this section, so add }
                     { a new section.                                 }
                     SectionRec.NextPos:=FileSize (DupeFile);
                     SectionRec.Age:=GetCurrentUnixTime;

                     { write the header of this section to disk }
                     Seek (DupeFile,SectionPos);
                     {$I-} BlockWrite (DupeFile,SectionRec,SECTION_REC_STATIC); {$I+} IORes:=IOResult;
                     IF (IORes <> 0) THEN
                        LogDiskIOError (IORes,'Error creating new section (1)');

                     SectionPos:=FileSize (DupeFile);
                     InitSectionRec (SectionRec.Sum);

                     { will be written to disk below }
                END;

                { add the DupeValue to this section }
                Inc (SectionRec.Count);
                SectionRec.Values[SectionRec.Count]:=DupeValue;

                { update the total of all section }
                IF (SectionRec.Sum < 65535) THEN
                   Inc (SectionRec.Sum);

                {$IFDEF DebugDupeCheck}
                LogMessage ('Added dupe value $'+Long2HexString (DupeValue)+
                            ' at offset '+Word2String (SectionRec.Count)+
                            ' in section at $'+Long2HexString (SectionPos));
                {$ENDIF}

                { write the section to disk }
                Seek (DupeFile,SectionPos);
                {$I-} BlockWrite (DupeFile,SectionRec,SectionRec.Length); {$I+} IORes:=IOResult;
                IF (IORes <> 0) THEN
                   LogDiskIOError (IORes,'Error writing to Dupe Database');

                Exit; { done }
           END;

           { go to the next section }
           SectionPos:=SectionRec.NextPos;

     UNTIL (SectionPos = 0{never happens ;});
END;


{--------------------------------------------------------------------------}
{ DupeCheck_PackDupeBase                                                   }
{                                                                          }
{ This routine is called by WtrUtil to remove old and unlinked records     }
{ from the dupe database. This is done by creating a new database,         }
{ following all the area records and copying all not overaged records,     }
{ calculating the new Sum values for each on the fly. The Area Records are }
{ updated with a new DupeList record where required. Small records are     }
{ compressed into bigger records.                                          }
{ Dupe Database and other databases must be open on call.                  }
{ Return values are 0=failed, 1=success, 2=was not open, skipped packing.  }
{                                                                          }
FUNCTION DupeCheck_PackDupeBase : BYTE;

VAR NewDupe   : FILE;
    DelFile   : FILE;
    IORes     : BYTE;
    Header    : STRING[28];

    AreaLp    : AreaBaseRecordNrType;
    AreaRec   : AreaBaseRecord;

    DupePos   : LONGINT;
    NextPos   : LONGINT;
    BytesRead : WordLong;

    NewSum    : LONGINT;
    OldestAge : LONGINT;

    NewPos    : LONGINT;
    PrevPos   : LONGINT;

    OldSize   : LONGINT;
    NewSize   : LONGINT;
    Improve   : BYTE;

LABEL WriteAreaRec;

BEGIN
     IF (NOT DupeIsOpen) THEN
     BEGIN
          DupeCheck_PackDupeBase:=2; { nothing to check }
          Exit;
     END;

     DupeCheck_PackDupeBase:=0; { assume error }

     OldSize:=FileSize (DupeFile);

     { create a new dupe database to create new records in }
     Assign (NewDupe,Config.SystemDir+'DUPEBASE.$$$');
     {$I-} ReWrite (NewDupe,1); {$I+} IORes:=IOResult;
     IF (IORes <> 0) THEN
     BEGIN
          LogDiskIOError (IORes,'Error creating temporary '+Config.SystemDir++'DUPEBASE.$$$');
          Exit; { with 0: error }
     END;

     {$IFDEF LogFileIO}PostOpenF (NewDupe);{$ENDIF}

     { write the header to the new base }
     Header:=DUPE_HEADER;
     BlockWrite (NewDupe,Header[1],28);

     { keep sum good looking statistics }
     {AgeKilled:=0;}

     { run through each of the areabase records and follow the dupelist }
     { for them.                                                        }
     FOR AreaLp:=1 TO AreaBaseRecCount DO
     BEGIN
          ReadAreaBaseRecord (AreaLp,AreaRec);

          { count all sections we keep to form the new Sum }
          NewSum:=0;

          IF (AreaRec.DupeAge = 0) THEN
          BEGIN
               { no dupes wanted - kill off the old ones }
               IF (AreaRec.DupeList = 0) THEN
                  Continue; { already OK }

               LogMessage (liGeneral,'Dupe checking disable; removing dupe records for area');
               LogExtraMessage (AreaRec.AreaName_F);

               AreaRec.DupeList:=0; { is written to disk below }
               GOTO WriteAreaRec;
          END;

          { they want us to keep track of dupes for this area }
          { see if we have stored any to both about in this run }

          IF (AreaRec.DupeList = 0) THEN
             Continue; { nothing to process }

          { see if it is valid (user didn't remove DupeBase) }
          { (old to avoid having SetAreaRef complain).       }
          IF (AreaRec.DupeList < 28) OR (AreaRec.DupeList+SECTION_REC_STATIC > FileSize (DupeFile)) THEN
          BEGIN
               LogMessage (liFatal,'Dupe Database consistency error, resetting dupes record for area');
               LogExtraMessage (AreaRec.AreaName_F);
               AreaRec.DupeList:=0;
               GOTO WriteAreaRec;
          END;

          CalcAreaRef (AreaRec);

          { calculate the oldest age we want to keep }
          OldestAge:=GetCurrentUnixTime-AreaRec.DupeAge*7*SEC_Dag;

          { follow all the dupe records and count and then }
          { copy all not outdated records.                 }
          DupePos:=AreaRec.DupeList;

          { start a new area list }
          AreaRec.DupeList:=0;

          WHILE (DupePos <> 0) DO
          BEGIN
               Seek (DupeFile,DupePos);
               BlockRead (DupeFile,SectionRec,SizeOf (DupeSectionRecord),BytesRead);

               { check for inconsistencies }
               IF (BytesRead < SECTION_REC_STATIC) OR
                  (BytesRead < SectionRec.Length) OR
                  (SectionRec.AreaRef <> AreaRef) OR
                  (SectionRec.Count > MAX_VALUES_PER_SECTION{1024}) OR
                  (SectionRec.Count > (SectionRec.Length-SECTION_REC_STATIC) DIV 4) THEN
               BEGIN
                    LogMessage (liFatal,'Dupe Section $'+Long2HexString (DupePos)+
                                ' is corrupted; aborted chain');
                    Break; { from the while }
               END;

               NextPos:=SectionRec.NextPos;

               { check for outdated record }
               IF (SectionRec.Age > 0) AND (SectionRec.Age < OldestAge) THEN
               BEGIN
                    { dump this dupe record }
                    {Inc (AgeKilled,SectionRec.Count);}

                    {$IFDEF DebugDupeCheck}
                    LogMessage ('$'+Long2HexString (DupePos)+
                                ' killed ('+Word2String (SectionRec.Count)+
                                ', dated '+UnixTimeToString (SectionRec.Age)+')');
                    {$ENDIF}
               END ELSE
               BEGIN
                    { keep the dupes in this record }
                    Inc (NewSum,SectionRec.Count);
                    SectionRec.Sum:=NewSum;

                    SectionRec.NextPos:=0;

                    { write to new section record }
                    NewPos:=FileSize (NewDupe);

                    Seek (NewDupe,FileSize (NewDupe));
                    BlockWrite (NewDupe,SectionRec,SectionRec.Length);

                    {$IFDEF DebugDupeCheck}
                    LogMessage ('$'+Long2HexString (DupePos)+
                                ' kept ('+Word2String (SectionRec.Count)+
                                ' dated '+UnixTimeToString (SectionRec.Age)+')');
                    {$ENDIF}

                    IF (AreaRec.DupeList = 0) THEN
                       AreaRec.DupeList:=NewPos
                    ELSE BEGIN
                         { there is a previous record }
                         { link from it to the new record }

                         { read the previos record (static part only) }
                         Seek (NewDupe,PrevPos);
                         BlockRead (NewDupe,SectionRec,SECTION_REC_STATIC);

                         { link it to the new record }
                         SectionRec.NextPos:=NewPos;

                         { write it back to disk }
                         Seek (NewDupe,PrevPos);
                         BlockWrite (NewDupe,SectionRec,SECTION_REC_STATIC);
                    END;

                    { remember the new position for linking }
                    { from the next time.                   }
                    PrevPos:=NewPos;
               END;

               { continue processing from the old dupe chain at }
               { the saved next record position.                }
               DupePos:=NextPos;
          END; { while }

      WriteAreaRec:

          { update the area record }
          WriteAreaBaseRecord (AreaLp,AreaRec);

          {$IFDEF DebugDupeCheck}
          LogMessage ('Area '+AreaRec.AreaName_U+': Dupes Stored='+Longint2String (NewSum));
          {$ENDIF}
     END;

     { finished. close the new dupe base, delete the old one and rename }
     { the new one in place.                                            }

     { close new one }
     NewSize:=FileSize (NewDupe);
     {$IFDEF LogFileIO}PreCloseF (NewDupe);{$ENDIF}
     Close (NewDupe);

     { close old one }
     DupeCheckEnd;

     { delete the old .OLD one }
     Assign (DelFile,Config.SystemDir+'DUPEBASE.OLD');
     {$I-} Erase (DelFile); {$I+} IORes:=IOResult;
     IF (IORes <> 0) AND (IORes <> 2) THEN
     BEGIN
          LogDiskIOError (IORes,'Error deleting old backup file '+Config.SystemDir+'DUPEBASE.OLD');
          Exit;
     END;

     { rename old one to .OLD }
     Assign (DelFile,Config.SystemDir+'DUPEBASE.TDB');
     {$I-} Rename (DelFile,Config.SystemDir+'DUPEBASE.OLD'); {$I+} IORes:=IOResult;
     IF (IORes <> 0) THEN
     BEGIN
          LogDiskIOError (IORes,'Error renaming '+Config.SystemDir+'DUPEBASE.TDB to DUPEBASE.OLD');
          Exit;
     END;

     { rename new one }
     {$I-} Rename (NewDupe,Config.SystemDir+'DUPEBASE.TDB'); {$I+} IORes:=IOResult;
     IF (IORes <> 0) THEN
     BEGIN
          LogDiskIOError (IORes,'Failed to rename new '+Config.SystemDir++'DUPEBASE.$$$ to '+Config.SystemDir+'DUPEBASE.TDB');
          Exit;
     END;

     { open new one }
     DupeCheckInit;

     { report statistics }
     IF (OldSize = 0) THEN
        Improve:=0 { niets verwijderd van niets }
     ELSE
         Improve:=Round ((OldSize-NewSize)/OldSize*100);

     LogExtraMessage ('        DupeBase: now '+Longint2String (FileSize (DupeFile) DIV 1024)+
                      'Kb, saved '+Byte2String (Improve)+'%');

     { done! }
     DupeCheck_PackDupeBase:=1; { success }
END;


{--------------------------------------------------------------------------}
{ Unit Initialization                                                      }
{                                                                          }
BEGIN
     DupeIsOpen:=FALSE;
END.

(* old..
{--------------------------------------------------------------------------}
{ DupeCheckExist                                                           }
{                                                                          }
{ De ingevoerde CRC32 waarde over het bericht wordt door deze routine      }
{ vergeleken met de in de dupe tabel aanwezige getallen. Als het getal er  }
{ in voorkomt wordt TRUE terug gegeven. Anders FALSE en wordt het getal    }
{ aan het eind toegevoegd.                                                 }
{                                                                          }
FUNCTION DupeCheckExist (CRC : LONGINT) : BOOLEAN;

VAR Found : BOOLEAN;
    Lp    : WORD;

BEGIN
     { scan de tabel in het geheugen }
     Found:=FALSE;

     {$IFDEF O S2}
     ASM
        MOV EDI,DupeMemoryPtr    { adres van het buffer ophalen }
        ADD EDI,4                { eerste is de opslag pointer, sla die over }

        XOR ECX,ECX              { reset high part }
        MOV CX,Config.DupeChecks { aantal te doorzoeken }

        MOV EAX,CRC

        CLD                      { vooruit zoeken }
      @Cont:
        REPNE SCASD              { zoek naar EAX }
        JCXZ @NotFound           { einde tabel, niet gevonden dus }
                                 { stonden we het ten tijd van de controle niet }

      @Found:
        MOV Found,TRUE           { gevonden! maak de locale var Found TRUE }

      @NotFound:
     END;
     {$ELSE}
     ASM
        LES DI,DupeMemoryPtr { adres van het buffer ophalen }
        ADD DI,4             { eerste is de opslag pointer, sla die over }
        MOV CX,Config.DupeChecks { aantal te doorzoeken }
        SHL CX,1             { *2 omdat we in words zoeken }

        LEA SI,CRC           { hierop controleren }
        SEGSS MOV AX,[SI]    { bx:ax laden met de CRC }
        SEGSS MOV BX,[SI+2]

        CLD                  { vooruit zoeken }
      @Cont:
        REPNE SCASW          { zoek naar AX }
        JCXZ @NotFound       { einde tabel, niet gevonden dus }

        PUSH AX              { wel gevonden, kijk of we aan het begin }
        MOV AX,DI            { van een longint staan, dan is het verkeerd }
        AND AL,3             { omdat SCASW altijd verhoogd controleren we }
        POP AX               { dus of we NU wel aan het begin staan, dan }
                             { stonden we het ten tijd van de controle niet }
        JE @Cont             { niet aan het begin, zoek verder }

        CMP BX,ES:[DI]       { kijk of het tweede deel ook klopt }
        JNE @Cont            { nee, zoek verder }

      @Found:
        MOV Found,TRUE       { gevonden! maak de locale var Found TRUE }

      @NotFound:
     END;
     {$ENDIF}

     DupeCheckExist:=Found;
     IF Found THEN Exit;

     { niet gevonden, bewaar voor verder controle }
     { RWI 950313: = veranderd in >= }
     IF (DupeMemoryPtr^[0] >= Config.DupeChecks) THEN
        DupeMemoryPtr^[0]:=1
     ELSE
         Inc (DupeMemoryPtr^[0]);

     DupeMemoryPtr^[DupeMemoryPtr^[0]]:=CRC;
END;
*)
