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

{$i platform.inc}

{ Routines om WtrGate ook naar de JAM base te laten schrijven.           }
{ Gebasseerd op informatie uit het 'JAM message base proposal'           }
{                                                                        }
{       JAM(mbp) - The Joaquim-Andrew-Mats Message Base Proposal         }
{                                                                        }
{ Copyright 1993 Joaquim Homrighausen, Andrew Milner, Mats Birch, and    }
{                     Mats Wallin. ALL RIGHTS RESERVED.                  }
{                                                                        }
{ MD 10-09-93 In luxemburg de API opgepikt, en begonnen met een          }
{             stuk try-out code, object georienteerd omdat ik van plan   }
{             ben veel meer binnen objecten in te kapselen.              }

INTERFACE

USES Database;

PROCEDURE Jam_Init;
PROCEDURE Jam_Done;

PROCEDURE Jam_ImportMessage (Area_Name,Area_Path,DecodePath : STRING; DecodeFiles : BOOLEAN);
PROCEDURE Jam_ScanArea (AreaRecNr : AreaBaseRecordNrType; AreaData : AreaBaseRecord; IsPrimaryNetmailArea,IsBadArea : BOOLEAN);

FUNCTION  Jam_LinkArea (AreaRec : AreaBaseRecord) : BOOLEAN;
PROCEDURE Jam_PackArea (AreaData : AreaBaseRecord);
PROCEDURE Jam_RenumberArea (VAR AreaRec : AreaBaseRecord);
PROCEDURE Jam_ReIndexArea (AreaData : AreaBaseRecord);

PROCEDURE Jam_Rescan (VAR AreaData : AreaBaseRecord);


IMPLEMENTATION

USES Cfg,
     Msgs,
     Fido,
     DList,
     Logs,
     Globals,
     Start,
     Decode,
     FBuffer,
     PackBuf,
     Trans,
     {Stats,}
     Scan,
     UnixTime,
     MsgUtil,
     Slice,
     Dos,
     Crt,
     Ramon,
     Rescan,
     UserBase,          {## check again }
     SwapMem;           {## check again }

TYPE JAMBuf       = ARRAY[0..$FFFD] OF CHAR;
     FILENAMETYPE = STRING[200];
     ASCIIZ       = ARRAY[1..255] OF CHAR;
     ARRAY4       = ARRAY[1..4] OF CHAR;

CONST JamBaseMaximumLockTrys = 50;

CONST EXT_HDRFILE = '.JHR';
      EXT_TXTFILE = '.JDT';
      EXT_IDXFILE = '.JDX';
      EXT_LRDFILE = '.JLR';

      HEADERSIG : ARRAY4 = ('J','A','M',#0);

      CurrentRevLev = 001;

{-Header file information block, stored first in all .JHR files}

{-Message status bits}

CONST JMSG_LOCAL       = $00000001; {Msg created locally}
      JMSG_INTRANSIT   = $00000002; {Msg is in-transit}
      JMSG_PRIVATE     = $00000004; {Private}
      JMSG_READ        = $00000008; {Read by addressee}
      JMSG_SENT        = $00000010; {Sent to remote}
      JMSG_KILLSENT    = $00000020; {Kill when sent}
      JMSG_ARCHIVESENT = $00000040; {Archive when sent}
      JMSG_HOLD        = $00000080; {Hold for pick-up}
      JMSG_CRASH       = $00000100; {Crash}
      JMSG_IMMEDIATE   = $00000200; {Send Msg now, ignore restrictions}
      JMSG_DIRECT      = $00000400; {Send directly to destination}
      JMSG_GATE        = $00000800; {Send via gateway}
      JMSG_FILEREQUEST = $00001000; {File request}
      JMSG_FILEATTACH  = $00002000; {File(s) attached to Msg}
      JMSG_TRUNCFILE   = $00004000; {Truncate file(s) when sent}
      JMSG_KILLFILE    = $00008000; {Delete file(s) when sent}
      JMSG_RECEIPTREQ  = $00010000; {Return receipt requested}
      JMSG_CONFIRMREQ  = $00020000; {Confirmation receipt requested}
      JMSG_ORPHAN      = $00040000; {Unknown destination}
      JMSG_ENCRYPT     = $00080000; {Msg text is encrypted}
      JMSG_COMPRESS    = $00100000; {Msg text is compressed}
      JMSG_ESCAPED     = $00200000; {Msg text is seven bit ASCII}
      JMSG_FPU         = $00400000; {Force pickup}
      JMSG_TYPELOCAL   = $00800000; {Msg is for local use only (not for export)}
      JMSG_TYPEECHO    = $01000000; {Msg is for conference distribution}
      JMSG_TYPENET     = $02000000; {Msg is direct network mail}
      JMSG_NODISP      = $20000000; {Msg may not be displayed to user}
      JMSG_LOCKED      = $40000000; {Msg is locked, no editing possible}
      JMSG_DELETED     = $80000000; {Msg is deleted}

{-Message header}
{-Message header subfield types}

CONST JAMSFLD_OADDRESS    = 0;
      JAMSFLD_DADDRESS    = 1;
      JAMSFLD_SENDERNAME  = 2;
      JAMSFLD_RECVRNAME   = 3;
      JAMSFLD_MSGID       = 4;
      JAMSFLD_REPLYID     = 5;
      JAMSFLD_SUBJECT     = 6;
      JAMSFLD_PID         = 7;
      JAMSFLD_TRACE       = 8;
      JAMSFLD_ENCLFILE    = 9;    { path, no wildcards - can have multiple of these }
      JAMSFLD_ENCLFWALIAS = 10;
      JAMSFLD_ENCLFREQ    = 11;
      JAMSFLD_ENCLFILEWC  = 12;   { as 9, with wildcards - can have multiple }
      JAMSFLD_ENCLINDFILE = 13;   { points to ascii file with filenames on each line }
      JAMSFLD_EMBINDAT    = 1000;
      JAMSFLD_FTSKLUDGE   = 2000;
      JAMSFLD_SEENBY2D    = 2001;
      JAMSFLD_PATH2D      = 2002;
      JAMSFLD_FLAGS       = 2003;
      JAMSFLD_TZUTCINFO   = 2004;
      JAMSFLD_UNKNOWN     = $FFFF;

{-Message header subfield}

TYPE JAMSUBFIELDPTR = ^JAMSUBFIELD;
     JAMSUBFIELD    = RECORD
                            LoID   : WORD;   {Field ID, 0 - $ffff}
                            Buffer : STRING; {Data buffer}
                      END; {JAMSUBFIELD}

     JAMBINSUBFIELDPTR = ^JAMSUBFIELD;
     JAMBINSUBFIELD    = RECORD
                               LoID   : WORD;    {Field ID, 0 - $ffff}
                               HiID   : WORD;    {Reserved for future use}
                               DatLen : LONGINT; {Length of buffer that follows}
                         END; {JAMSUBFIELD}

{-Message index record}

     JAMIDXREC = RECORD
                       UserCRC   : LONGINT; {CRC-32 of destination username (lowercase)}
                       HdrOffset : LONGINT; {Offset of header in .JHR file}
                 END; {JAMIDXREC}

TYPE JAMHDRINFO = RECORD
                        Signature   : ARRAY4;  { <J><A><M> followed by <NUL> }
                        DateCreated : LONGINT; { Creation date }
                        ModCounter  : LONGINT; { Last processed counter }
                        ActiveMsgs  : LONGINT; { Number of active (not deleted) messages }
                        PasswordCRC : LONGINT; { CRC-32 of password to access, or -1 if none }
                        BaseMsgNum  : LONGINT; { Lowest message number in index file }
                        Reserved    : ARRAY[1..1000] OF CHAR;
                  END; {JAMHDRINFO}

     JAMHDR = RECORD
                    Signature     : ARRAY4;    { <J><A><M> followed by <NUL> }
                    Revision      : WORD;      { CurrentRevLev }
                    ReservedWord  : WORD;      { Reserved for future use }
                    SubfieldLen   : LONGINT;   { Length of subfields }
                    TimesRead     : LONGINT;   { Number of times message read }
                    MsgIdCRC      : LONGINT;   { CRC-32 of MSGID line }
                    ReplyCRC      : LONGINT;   { CRC-32 of REPLY line }
                    ReplyTo       : LONGINT;   { This msg is a reply to.. }
                    Reply1st      : LONGINT;   { First reply to this msg }
                    ReplyNext     : LONGINT;   { Next msg in reply chain }
                    DateWritten   : LONGINT;   { When msg was written }
                    DateReceived  : LONGINT;   { When msg was received/read }
                    DateProcessed : LONGINT;   { When msg was processed by packer }
                    MsgNum        : LONGINT;   { Message number (1-based) }
                    Attribute     : LONGINT;   { Msg attribute, see "Status bits" }
                    Attribute2    : LONGINT;   { Reserved for future use }
                    TxtOffset     : LONGINT;   { Offset of text in text file }
                    TxtLen        : LONGINT;   { Length of message text }
                    PasswordCRC   : LONGINT;   { CRC-32 of password to access msg }
                    Cost          : LONGINT;   { Cost of message }
              END; {JAMHDR}

     JLRRecord = RECORD
                       UserCRC     : LONGINT;
                       UserID      : LONGINT;
                       LastReadMsg : LONGINT;
                       HighReadMsg : LONGINT;
                 END;


VAR AreaName : STRING;
    AreaPath : STRING;

    Jam_JHR_header : JAMHDRINFO;
    Jam_MSG_header : JAMHDR;

    JamSubFieldList     : List;
    TotalSubFieldSize   : LONGINT;
    JamMsgHeaderOffset,
    JamMsgSize          : LONGINT;

    ImportMsgs          : BOOLEAN;  { Lees niet alleen bericht headers }
                                    { maar ook naar het MSG formaat.   }
    HdrFile,
    IdxFile,
    TxtFile             : FILE;

    CurrentMessage,
    CurrentHeader       : LONGINT;

    IsOpen              : BOOLEAN;
    TouchCounter        : BYTE;

    SplitParts,
    SplitCurrent        : WORD;


{ Strategie :                                                        }
{                                                                    }
{   Creer een JamBase :   1. Schrijf in HeaderInfo naar *.JHR        }
{                         2. Creer nul files voor *.JDT              }
{                                                 *.JDX              }
{                                                 *.JLR              }
{                                                                    }
{   Schrijf Bericht   :   1. Lock de Base                            }
{                         2. Append headerinfo                       }
{                         3. Append Text                             }
{                         4. Append Index                            }
{                         5. Geef base weer vrij                     }
{                                                                    }
{   Lees bericht      :   1. Lees Headers                            }
{                         2. Lees bericht                            }


{--------------------------------------------------------------------------}
{ LockBase                                                                 }
{                                                                          }
{ Probeert een gegeven aantal maal de area base af te sluiten, de routine  }
{ geeft het resultaat via een boolean terug.                               }
{                                                                          }
FUNCTION LockBase : BOOLEAN;

VAR LockTry : WORD;

BEGIN
     { simpel no fuss }
     LockBase:=TRUE;
     IF LockFile (HdrFile) THEN
        Exit;

     { problem! }
     Message ('[JAM] Found '+Areaname+' locked, retrying');

     FOR LockTry:=1 TO JamBaseMaximumLockTrys DO
     BEGIN
          IF LockFile (HdrFile) THEN
             Break;

          Slice_Now;
          DelayOneSecond;
     END;

     WindowPop; { haal bericht weg }

     { als het niet gelukt is, geef dat dan ff door }
     IF (LockTry >= JamBaseMaximumLockTrys) THEN
        LockBase:=FALSE;
END;


{--------------------------------------------------------------------------}
{ UnlockBase                                                               }
{                                                                          }
{ Geeft de Jam Base weer vrij voor andere programma's.                     }
{                                                                          }
FUNCTION UnlockBase : BOOLEAN;
BEGIN
     UnlockBase:=UnlockFile (hdrFile);
END;


{--------------------------------------------------------------------------}
{ GetNextMessage                                                           }
{                                                                          }
{ Leest het volgende bericht in, controleert of het niet een lege          }
{ entry is en zet de HdrFile pointer op de juiste plaatst.                 }
{                                                                          }
FUNCTION GetNextMessage : BOOLEAN;

VAR IdxRec : JAMIDXREC;
    IORes  : BYTE;

BEGIN
     GetNextMessage:=FALSE;

     WHILE (NOT Eof (IdxFile)) DO
     BEGIN
          {$I-} BlockRead (IdxFile,IdxRec,SizeOf (IdxRec)); {$I+} IORes:=IOResult;
          IF (IORes <> 0) THEN
             Exit;

          { Lege index posities hebben een positie met de waarde $FFFFFFFF }
          { deze skippen we.                                               }
          IF (IdxRec.HdrOffSet <> $FFFFFFFF) THEN
          BEGIN
               GetNextMessage:=TRUE;
               {$I-} Seek (HdrFile,IdxRec.hdrOffset); {$I+}
               IORes:=IOResult; { negeer het resultaat }
               Break; { uit de while }
          END;
     END; { while }

     CurrentMessage:=(FilePos (IdxFile) DIV SizeOf (IdxRec))+Jam_JHR_Header.basemsgnum;
     CurrentHeader:=IdxRec.HdrOffset;
END;


{--------------------------------------------------------------------------}
{ GetFirstMessage                                                          }
{                                                                          }
{ Zet de index pointer op het begin van de indexfile, en laat              }
{ GetNextMessage de echte entry inlezen.                                   }
{                                                                          }
FUNCTION GetFirstMessage : BOOLEAN;

VAR IORes : BYTE;

BEGIN
     {$I-} Seek (IdxFile,0); {$I+} IORes:=IOResult;

     IF (IORes <> 0) OR (FileSize (IdxFile) = 0) THEN
        GetFirstMessage:=FALSE
     ELSE
         GetFirstMessage:=GetNextMessage;
END;


{--------------------------------------------------------------------------}
{ FindByIndex                                                              }
{                                                                          }
{ Zoekt een bericht nummer op in de index tabel en plaatst daar de         }
{ filepointer van de headerfile op.                                        }
{                                                                          }
FUNCTION FindByIndex (Msg : LONGINT) : BOOLEAN;

VAR RealMsg : LONGINT;
    IdxRec  : JAMIDXREC;
    IORes   : BYTE;

BEGIN
     FindByIndex:=FALSE;
     RealMsg:=Msg-Jam_JHr_header.baseMsgNum;

     IF (RealMsg < 0) THEN
        Exit;

     {$I-} Seek (IdxFile,SizeOf (JAMIDXREC)*RealMsg); {$I+} IORes:=IOResult;

     IF (IORes = 0) THEN
     BEGIN
          {$I-} BlockRead (IdxFile,IdxRec,SizeOf (IdxRec)); {$I+} IORes:=IOResult;
     END;

     IF (IORes = 0) THEN
     BEGIN
          {$I-} Seek (HdrFile,IdxRec.HdrOffset); {$I+} IORes:=IOResult;
     END;

     CurrentHeader:=IdxRec.HdrOffset;

     IF (IORes <> 0) THEN
     BEGIN
          LogDiskIOError (IORes,'[JAM] Error reading index record');
          Exit;
     END;

     FindByIndex:=TRUE;
END;


{--------------------------------------------------------------------------}
{ ReadJamHeader                                                            }
{                                                                          }
{ Leest de statische header van de Jam base *.JHR file.                    }
{                                                                          }
FUNCTION ReadJamHeader : WORD;
BEGIN
    {$I-}
    IF (FilePos (HdrFile) <> 0) THEN
       Seek (HdrFile,0);

    BlockRead (HdrFile,Jam_JHR_header,SizeOf (Jam_JHR_header));
    {$I+}

    ReadJamHeader:=IOResult;
END;


{--------------------------------------------------------------------------}
{ SaveFile                                                                 }
{                                                                          }
{ Deze functie opent een file, voegt daar gegevens aan toe, en sluit deze  }
{ weer. Het resultaat wordt terug gegeven.                                 }
{                                                                          }
FUNCTION SaveFile (FileName : STRING; VAR Buffer; Length : LONGINT) : WORD;

VAR TmpFile : FILE;

BEGIN
     Assign (TmpFile,FileName);
     {$I-}
     ReWrite (TmpFile,1);
     {$IFDEF LogFileIO}PostOpenF (TmpFile);{$ENDIF}

     BlockWrite (TmpFile,Buffer,Length);

     {$IFDEF LogFileIO}PreCloseF (TmpFile);{$ENDIF}
     Close (TmpFile);
     {$I+}
     SaveFile:=IOResult;
END;


{--------------------------------------------------------------------------}
{ CreateBase                                                               }
{                                                                          }
{ Creert een nieuwe JamBase, door een header file te creeren, en enkele    }
{ lege bestanden.                                                          }
{                                                                          }
FUNCTION CreateBase : BOOLEAN;

VAR IORes : BYTE;

BEGIN
     Inc (TouchCounter);

     { Vul de file header met informatie en    }
     { schrijf deze naar disk door het creeren }
     { van nieuwe JamBase files.               }
     WITH Jam_JHR_header DO
     BEGIN
          Signature:=HeaderSig;   { JAM#0 }
          DateCreated:=GetCurrentUnixTime;
          ModCounter:=0;
          ActiveMsgs:=0;
          PasswordCRC:=$FFFFFFFF;
          BaseMsgNum:=1;
          FillChar (Reserved,1000,0);
     END; { with }

     { Schrijf Header info file naar disk }
     IORes:=SaveFile (AreaPath+EXT_HDRFILE,Jam_JHR_header,SizeOf (Jam_JHR_header));

     { Schrijf Index,TextBody en LastRead files naar disk }
     IF (IORes = 0) THEN IORes:=SaveFile (AreaPath+EXT_TXTFILE,IORes,0);
     IF (IORes = 0) THEN IORes:=SaveFile (AreaPath+EXT_IDXFILE,IORes,0);
     IF (IORes = 0) THEN IORes:=SaveFile (AreaPath+EXT_LRDFILE,IORes,0);

     IF (IORes <> 0) THEN
     BEGIN
          LogDiskIOError (IORes,'[JAM] Error creating messagebase '+AreaPath);
          CreateBase:=FALSE;
     END ELSE
     BEGIN
          CreateBase:=TRUE;
          LogMessage (liTrivial,'Created JAM messagebase for '+AreaName);
     END;
END;


{-------------------------------------------------------------------------}
{ AttrFtn2Jam                                                             }
{                                                                         }
{ Vertaald Fido attributen naar JAM base flaggen.                         }
{                                                                         }
FUNCTION AttrFtn2Jam : LONGINT;

VAR TempFlag : LONGINT;

BEGIN
     TempFlag:=0;

     { Vertaal standaard FTN flag velden }

     { Private }
     IF ((Msg.Attr_F AND MSGPRIVATE) <> 0) THEN
        TempFlag:=TempFlag OR JMSG_PRIVATE;

     { Read }
     IF ((Msg.Attr_F AND MSGREAD) <> 0) THEN
        TempFlag:=TempFlag OR JMSG_READ;

     { Sent }
     IF ((Msg.Attr_F AND MSGSENT) <> 0) THEN
        TempFlag:=TempFlag OR JMSG_SENT;

     { File Attach }
     IF ((Msg.Attr_F AND MSGFILE) > 0) THEN
        TempFlag:=TempFlag OR JMSG_FILEATTACH;

     { Kill / Sent }
     IF ((Msg.Attr_F AND MSGKILL) <> 0) THEN
        TempFlag:=TempFlag OR JMSG_KILLSENT;

     { Local }
     IF ((Msg.Attr_F AND MSGLOCAL) <> 0) THEN
        TempFlag:=TempFlag OR JMSG_LOCAL;

     { File request }
     IF ((Msg.Attr_F AND MSGFRQ) <> 0) THEN
        TempFlag:=TempFlag OR JMSG_FILEREQUEST;

     { Return Request }
     IF ((Msg.Attr_F AND MSGRRQ) <> 0) THEN
        TempFlag:=TempFlag OR JMSG_RECEIPTREQ;

     IF ((Msg.Attr_F AND MSGORPHAN) <> 0) THEN
        TempFlag:=TempFlag OR JMSG_ORPHAN;

     IF ((Msg.ExtAttr_F AND EXTMSGLOK) <> 0) THEN
        TempFlag:=TempFlag OR JMSG_LOCKED;

  { Vertaal FSC-0053 velden }
  IF (Msg.ExtAttr_F AND EXTMSGHLD)>0 THEN   { Hold           }
   TempFlag := TempFlag OR JMSG_HOLD;
  IF (Msg.ExtAttr_F AND EXTMSGCRA)>0 THEN   { Crash          }
   TempFlag := TempFlag OR JMSG_CRASH;
  IF (Msg.ExtAttr_F AND EXTMSGK_S)>0 THEN   { Kill/Sent      }
   TempFlag := TempFlag OR JMSG_KILLSENT;
  IF (Msg.ExtAttr_F AND EXTMSGSNT)>0 THEN   { Sent           }
   TempFlag := TempFlag OR JMSG_SENT;
  IF (Msg.ExtAttr_F AND EXTMSGRCV)>0 THEN   { Received       }
   TempFlag := TempFlag OR JMSG_READ;
  IF (Msg.ExtAttr_F AND EXTMSGA_S)>0 THEN   { Archive Sent   }
   TempFlag := TempFlag OR JMSG_ARCHIVESENT;
  IF (Msg.ExtAttr_F AND EXTMSGDIR)>0 THEN   { Direct         }
   TempFlag := TempFlag OR JMSG_DIRECT;
  IF (Msg.ExtAttr_F AND EXTMSGFIL)>0 THEN   { File Attach    }
   TempFlag := TempFlag OR JMSG_FILEATTACH;
  IF (Msg.ExtAttr_F AND EXTMSGFRQ)>0 THEN   { File Request   }
   TempFlag := TempFlag OR JMSG_FILEREQUEST;
  IF (Msg.ExtAttr_F AND EXTMSGIMM)>0 THEN   { Immediate      }
   TempFlag := TempFlag OR JMSG_IMMEDIATE;
  IF (Msg.ExtAttr_F AND EXTMSGKFS)>0 THEN   { Kill/File/Sent }
   TempFlag := TempFlag OR JMSG_KILLFILE;
  IF (Msg.ExtAttr_F AND EXTMSGRRQ)>0 THEN   { ReceiptReq     }
   TempFlag := TempFlag OR JMSG_RECEIPTREQ;
  IF (Msg.ExtAttr_F AND EXTMSGCFM)>0 THEN   { Confirmation   }
   TempFlag := TempFlag OR JMSG_CONFIRMREQ;

     { Echomail / Lokaal? / Netmail }
     IF (Msg.Ready_F IN [Echomail,Local_Echomail]) THEN
        TempFlag:=TempFlag OR JMSG_TYPEECHO
     ELSE
         TempFlag:=TempFlag OR JMSG_TYPENET;

     AttrFtn2Jam:=TempFlag;
END;


{------------------------------------------------------------------------}
{ AttrJam2Ftn                                                            }
{                                                                        }
{ Vertaald de attributen in een Jam Flags veld naar een standaard        }
{ fido vlag veld. Voorlopig worden hier een hoop vlaggen                 }
{ genegeerd.                                                             }
{                                                                        }
FUNCTION AttrJam2Ftn : WORD;

VAR TempFlag : WORD;

BEGIN
     TempFlag:=0;

     { Strip local }
     IF ((Jam_Msg_Header.Attribute AND JMSG_LOCAL) <> 0) THEN
        TempFlag:=TempFlag OR MSGLOCAL;

     IF ((Jam_Msg_Header.Attribute AND JMSG_PRIVATE) <> 0) THEN
        TempFlag:=TempFlag OR MSGPRIVATE;

     IF ((Jam_Msg_Header.Attribute AND JMSG_CRASH) <> 0) THEN
        TempFlag:=TempFlag OR MSGCRASH;

     IF ((Jam_Msg_Header.Attribute AND JMSG_READ) <> 0) THEN
        TempFlag:=TempFlag OR MSGREAD;

     IF ((Jam_Msg_Header.Attribute AND JMSG_FILEATTACH) <> 0) THEN
        TempFlag:=TempFlag OR MSGFILE;

     IF ((Jam_Msg_Header.Attribute AND JMSG_ORPHAN) <> 0) THEN
        TempFlag:=TempFlag OR MSGORPHAN;

     IF ((Jam_Msg_Header.Attribute AND JMSG_KILLSENT) <> 0) THEN
        TempFlag:=TempFlag OR MSGKILL;

     IF ((Jam_Msg_Header.Attribute AND JMSG_FILEREQUEST) <> 0) THEN
        TempFlag:=TempFlag OR MSGFRQ;

     IF ((Jam_Msg_Header.Attribute AND JMSG_RECEIPTREQ) <> 0) THEN
        TempFlag:=TempFlag OR MSGRRQ;

     AttrJam2Ftn:=TempFlag;
END;

{------------------------------------------------------------------------}
{ AttrJam2ExtFtn                                                         }
{                                                                        }
{ Vertaald de attributen in een Jam Flags veld naar een standaard        }
{ fido vlag veld. Voorlopig worden hier een hoop vlaggen                 }
{ genegeerd.                                                             }
{                                                                        }
FUNCTION AttrJam2ExtFtn : LONGINT;

VAR TempFlag : LONGINT;

BEGIN
     TempFlag:=0;

     IF ((Jam_Msg_Header.Attribute AND JMSG_PRIVATE) <> 0) THEN
        TempFlag:=TempFlag OR EXTMSGPVT;

     IF ((Jam_Msg_Header.Attribute AND JMSG_CRASH) <> 0) THEN
        TempFlag:=TempFlag OR EXTMSGCRA;

     IF ((Jam_Msg_Header.Attribute AND JMSG_READ) <> 0) THEN
        TempFlag:=TempFlag OR EXTMSGRCV;

     IF ((Jam_Msg_Header.Attribute AND JMSG_FILEATTACH) <> 0) THEN
        TempFlag:=TempFlag OR EXTMSGFIL;

     IF ((Jam_Msg_Header.Attribute AND JMSG_KILLSENT) <> 0) THEN
        TempFlag:=TempFlag OR EXTMSGK_S;

     IF ((Jam_Msg_Header.Attribute AND JMSG_FILEREQUEST) <> 0) THEN
        TempFlag:=TempFlag OR EXTMSGFRQ;

     IF ((Jam_Msg_Header.Attribute AND JMSG_RECEIPTREQ) <> 0) THEN
        TempFlag:=TempFlag OR EXTMSGRRQ;

     IF ((Jam_Msg_Header.Attribute AND JMSG_KILLFILE) <> 0) THEN
        TempFlag:=TempFlag OR EXTMSGKFS;

     AttrJam2ExtFtn:=TempFlag;
END;

{--------------------------------------------------------------------------}
{ GetHighMsgNum                                                            }
{                                                                          }
{ Geeft het hoogste berichtnummer terug.                                   }
{                                                                          }
FUNCTION GetHighMsgNum : LongInt;
BEGIN
     IF IsOpen THEN
        GetHighMsgNum:=Jam_JHR_header.BaseMsgNum+(FileSize (IdxFile) DIV SizeOf (JAMIDXREC))-1;
END;


{--------------------------------------------------------------------------}
{ SearchForCrcLines                                                        }
{                                                                          }
FUNCTION SearchForCrcLines (VAR Regel : STRING) : BOOLEAN; FAR;

VAR Hulp : STRING;

BEGIN
     IF (UpCaseString (Copy (Regel,1,8)) = #1'MSGID: ') THEN
     BEGIN
          Hulp:=LoCaseString (Copy (Regel,9,255));
          Jam_MSG_Header.MsgIdCRC:=UpdateCRC32 ($FFFFFFFF,Hulp[1],Length (Hulp));
     END;

     IF (UpCaseString (Copy (Regel,1,8)) = #1'REPLY: ') THEN
     BEGIN
          Hulp:=LoCaseString (Copy (Regel,9,255));
          Jam_MSG_Header.ReplyCRC:=UpdateCRC32 ($FFFFFFFF,Hulp[1],Length (Hulp));
     END;

     { keep on search until both have been found }
     SearchForCrcLines:=(Jam_MSG_Header.MsgIdCRC <> -1) AND
                        (Jam_MSG_Header.ReplyCRC <> -1);
END;


{--------------------------------------------------------------------------}
{ FillHeader                                                               }
{                                                                          }
{ Converteert de gegevens uit het interne bericht formaat naar eem JAM     }
{ header.                                                                  }
{                                                                          }
PROCEDURE FillMsgHeader;

VAR DT : DateTime;

BEGIN
     WITH Jam_MSG_header DO
     BEGIN
          Signature:=HeaderSig;
          Revision:=CurrentRevLev;
          ReservedWord:=0;
          SubfieldLen:=TotalSubFieldSize;
          TimesRead:=0;
          MsgIdCRC:=-1; { filled in below }
          ReplyCRC:=-1; { filled in below }
          ReplyTo:=0;
          Reply1st:=0;
          ReplyNext:=0;

          FidoDateTimeStr2DosDateTime (Msg.Date_F,DT);
          DateWritten:=DosDateTime2UnixDateTime (DT);

          DateReceived:=0;
          DateProcessed:=GetCurrentUnixTime;
          MsgNum:=GetHighMsgNum;
          Attribute:=AttrFtn2Jam;
          Attribute2:=0;

          IF (JamMsgSize > 0) THEN
          BEGIN
               TxtOffset:=FileSize (txtFile)-JamMsgSize;
               TxtLen:=JamMsgSize;
          END ELSE
          BEGIN
               TxtOffset:=0;
               TxtLen:=0;
          END;

          PasswordCRC:=$FFFFFFFF;
          Cost:=Msg.Cost_F;
     END; { with }

     MsgsForEach (Msg.HeaderTop_F,SearchForCrcLines);
END;


{--------------------------------------------------------------------------}
{ AddToSubField                                                            }
{                                                                          }
{ Voegt een subfield toe aan de lijst in het geheugen.                     }
{                                                                          }
PROCEDURE AddToSubField (Id : WORD; Buffer : STRING);

VAR Tmp : JAMSUBFIELDPTR;

BEGIN
     IF (Buffer[Length (Buffer)] = #13) THEN
        Delete (Buffer,Length (Buffer),1);

     GetMem (Tmp,SizeOf (JAMSUBFIELD));
     Tmp^.LoId:=Id;
     Tmp^.Buffer:=Buffer;
     JamSubFieldList.Add (Tmp);

     Inc (TotalSubFieldSize,Length (Buffer)+SizeOf (JAMBINSUBFIELD));
END;


{--------------------------------------------------------------------------}
{ Fill_AddHeaderLine                                                       }
{                                                                          }
{ This routine is called for each line in the header and footer block and  }
{ searches for known lines. These are stored as a sub-field.               }
{                                                                          }
FUNCTION Fill_AddHeaderLine (VAR OrigRegel : STRING) : BOOLEAN; FAR;

VAR Regel : STRING;

BEGIN
     Fill_AddHeaderLine:=FALSE; { do not abort }

     Regel:=TransFix_HeaderLine (OrigRegel);

     CASE FidoGetKludgeID (Regel) OF
          klArea  : {de Area kludge wordt gestript};
                    {RAWI971006: zou niet meer langs moeten komen}

          { RWI 950605: berichten in de JAM base krijgen nu ook een }
          {             uniek MSGID per gespleten part.             }
          {## is nu al geregeld, dit moet weg}
          klMsgID :
              IF (SplitParts = 1) THEN
                 AddToSubField (0004,Copy (Regel,9,255))
              ELSE
                  {## should there not be an AKA here as well?}
                  AddToSubField (0004,GetFidoPktName);

          klReply :
              AddToSubField (0005,Copy (Regel,9,255));

          klPid :
              AddToSubField (0007,Copy (Regel,7,255));

          klFlags :
              AddToSubField (2003,Copy (Regel,7,255));

          {## INTL,TOPT & FMPT velden mogen niet bewaard worden! }
          ELSE
              { ^A weglaten }
              AddToSubField (2000,Copy (Regel,2,255)); { Alle andere kludges }
     END; { case }
END;


{--------------------------------------------------------------------------}
{ Fill_AddFooterLine                                                       }
{                                                                          }
{ This routine is called for each line in the header and footer block and  }
{ searches for known lines. These are stored as a sub-field.               }
{                                                                          }
{ Note that this is a special routine for the footer. There is no ELSE     }
{ block to store all unknown lines. These are instead added to the bottom  }
{ of the body of the message.                                              }
{                                                                          }
FUNCTION Fill_AddFooterLine (VAR Regel : STRING) : BOOLEAN; FAR;
BEGIN
     Fill_AddFooterLine:=FALSE; { do not abort }

     CASE FidoGetKludgeID (Regel) OF
          klSeenBy :
              IF (NOT Config.StripSeenBy) THEN
                 AddToSubField (2001,Copy (Regel,10,255));

          klPath :
              AddToSubField (2002,Copy (Regel,8,255));
     END; { case }
END;


{--------------------------------------------------------------------------}
{ FillSubArray                                                             }
{                                                                          }
{ Vult de subarray met regels uit het bericht.                             }
{                                                                          }
PROCEDURE FillSubFields;

VAR SubjectLine : STRING;

BEGIN
     AddToSubField (0000,Fido2Str (Msg.FromAddr_F));
     AddToSubField (0001,Fido2Str (Msg.Stored_ToAddr));
     AddToSubField (0002,Msg.FromUser_F);
     AddToSubField (0003,Msg.Stored_ToUser);

     IF ((Msg.Attr_F AND MSGFILE) = 0) THEN     { De subject line mag niet }
     BEGIN
          IF (SplitParts > 1) THEN
          BEGIN
               SubjectLine:='('+Byte2String (SplitCurrent)+
                            '/'+Byte2String (SplitParts)+
                            ') '+Msg.Subj_F;
               AddToSubField (2000,Copy (FidoCreateSplitLine (SplitCurrent,SplitParts),2,255));
          END ELSE
              SubjectLine:=Msg.Subj_F;

          AddToSubField (0006,SubjectLine);     { gebruikt worden voor     }
     END ELSE                                   { file attaches !          }
     BEGIN
          {## wrong behaviour: only ONE file per entry!}
          AddToSubField (0009,Msg.Subj_F);

          { RWI961213: Sommige programma's laten de filename niet in de }
          {            subject regel zien voor attached files...        }
          { RAWI980507: was setting field 0003 instead of 0006 }
          AddToSubField (0006,Msg.Subj_F);
     END;

     { Doorloop de fido header voor kludges en plaats ze in het goede  }
     { type SUBFIELD. Als we een header hebben, tenminste.             }
     MsgsForEach (Msg.HeaderTop_F,Fill_AddHeaderLine);
     MsgsForEach (Msg.CopiedHeadersTop_F,Fill_AddHeaderLine);

     { Doorloop de fido footer, voor kludges en plaats ze in het goede }
     { type SUBFIELD. Als er een footer is tenminste }
     MsgsForEach (Msg.FooterTop_F,Fill_AddFooterLine);
END;


{--------------------------------------------------------------------------}
{ CloseBase                                                                }
{                                                                          }
{ Geeft alle file handles en interne buffers weer vrij.                    }
{                                                                          }
PROCEDURE CloseBase;
BEGIN
     Inc (TouchCounter);

     AreaName:='';
     AreaPath:='';

     JamSubFieldList.Clear;

     IF (NOT IsOpen) THEN
        Exit;

     {$IFDEF LogFileIO}PreCloseF (HdrFile);{$ENDIF}
     Close (HdrFile);

     {$IFDEF LogFileIO}PreCloseF (TxtFile);{$ENDIF}
     Close (TxtFile);

     {$IFDEF LogFileIO}PreCloseF (IdxFile);{$ENDIF}
     Close (IdxFile);

     PeekFiles;

     IsOpen:=FALSE;
END;


{--------------------------------------------------------------------------}
{ OpenBase                                                                 }
{                                                                          }
{ Opent de base header, body en index ; en initialiseerd het object.       }
{                                                                          }
FUNCTION OpenBase (DenyAll : BOOLEAN; Area_Name,Area_Path : STRING) : BOOLEAN;

VAR IORes : BYTE;

BEGIN
     Area_Name:=DeleteFrontAndBackSpaces (Area_Name);
     Area_Path:=DeleteFrontAndBackSpaces (Area_Path);

     UpdateReadFile (Area_Path,0);

     { Zorg ervoor dat we alleen opnieuw reseten als de areaname anders is }
     IF IsOpen THEN
        { RWI 951023: Check is now performed on the path instead of the name }
        IF CaselessMatch (Area_Path,AreaPath) THEN
        BEGIN
             OpenBase:=TRUE; {RAWI980503: was missing}
             Exit;
        END ELSE
            CloseBase;

     Inc (TouchCounter);

     AreaName:=Area_Name;
     AreaPath:=Area_Path;
     OpenBase:=FALSE;

     (* RWI 960811: niet meer: te veel file access
     { Als de base nog niet bestaat, creer er dan een }
     IF (NOT BaseExists) THEN
        IF (NOT CreateBase) THEN
           Exit;
     *)

     IF DenyAll THEN
        FileMode:=fmReadWrite+fmDenyAll
     ELSE
         FileMode:=fmReadWrite+fmDenyNone;

     { Open de benodigde bestanden in DenyNone Share mode }
     Assign (HdrFile,AreaPath+EXT_HDRFILE);
     {$I-} Reset (HdrFile,1); {$I+} IORes:=IOResult;

     { RWI 960811: geen pre-existing check, dus *nu* aanmaken als ie mist }
     IF (IORes = 2) THEN
     BEGIN
          CreateBase;
          {$I-} Reset (HdrFile,1); {$I+} IORes:=IOResult;
     END;

     IF (IORes <> 0) THEN
     BEGIN
          IF (IORes = 3) THEN
             LogMessage (liGeneral,'[JAM] Path not found for '+AreaPath+EXT_HDRFILE)
          ELSE
              LogDiskIOError (IORes,'[JAM] Could not open/create '+AreaPath+EXT_HDRFILE);
          Exit;
     END;

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


     { Probeer de Area header in te lezen }
     IORes:=ReadJamHeader;
     IF (IORes <> 0) THEN
     BEGIN
          LogDiskIOError (IORes,'[JAM] Error reading header');

          {$IFDEF LogFileIO}PreCloseF (HdrFile);{$ENDIF}
          Close (HdrFile);
          PeekFiles;
          Exit;
     END;

     { Open de index file }
     Assign (IdxFile,AreaPath+EXT_IDXFILE);
     {$I-} Reset (IdxFile,1); {$I+} IORes:=IOResult;

     IF (IORes <> 0) THEN
     BEGIN
          { .JHR exists, create .JDX if "not found" }
          IF (IORes <> 2) THEN
          BEGIN
               LogDiskIOError (IORes,'[JAM] Error opening '+AreaPath+EXT_IDXFILE);
               {$IFDEF LogFileIO}PreCloseF (HdrFile);{$ENDIF}
               Close (HdrFile);
               PeekFiles;
               Exit;
          END;

          { IORes=2 }
          {$I-} ReWrite (IdxFile,1); {$I+} IORes:=IOResult;
          IF (IORes <> 0) THEN
          BEGIN
               LogDiskIOError (IORes,'[JAM] Error creating '+AreaPath+EXT_IDXFILE);
               {$IFDEF LogFileIO}PreCloseF (HdrFile);{$ENDIF}
               Close (HdrFile);
               PeekFiles;
               Exit;
          END;
     END;

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

     { Open de text file }
     Assign (TxtFile,AreaPath+EXT_TXTFILE);
     {$I-} Reset (TxtFile,1); {$I+} IORes:=IOResult;

     IF (IORes <> 0) THEN
     BEGIN
          IF (IORes <> 2) THEN
          BEGIN
               LogDiskIOError (IORes,'[JAM] Error opening '+AreaPath+EXT_TXTFILE);

               {$IFDEF LogFileIO}PreCloseF (IdxFile);{$ENDIF}
               Close (IdxFile);

               {$IFDEF LogFileIO}PreCloseF (HdrFile);{$ENDIF}
               Close (HdrFile);
               PeekFiles;
               Exit;
          END;

          { IORes=2 }
          {$I-} ReWrite (TxtFile,1); {$I+} IORes:=IOResult;
          IF (IORes <> 0) THEN
          BEGIN
               LogDiskIOError (IORes,'[JAM] Error creating '+AreaPath+EXT_TXTFILE);

               {$IFDEF LogFileIO}PreCloseF (IdxFile);{$ENDIF}
               Close (IdxFile);

               {$IFDEF LogFileIO}PreCloseF (HdrFile);{$ENDIF}
               Close (HdrFile);
               PeekFiles;
               Exit;
          END;
     END;

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

     { geef het resultaat terug }
     IsOpen:=TRUE;
     OpenBase:=TRUE;
END;


{--------------------------------------------------------------------------}
{ WriteIndexEntry                                                          }
{                                                                          }
{ Voegt een index entry toe aan het einde van het index bestand voor het   }
{ bericht zoals dat nu in het geheugen staat.                              }
{                                                                          }
FUNCTION WriteIndexEntry (Source : STRING; Offset : LONGINT) : BOOLEAN;

VAR TmpIdx : JAMIDXREC;
    Regel  : STRING;

BEGIN
     WriteIndexEntry:=FALSE; { assume failure }

     IF (NOT IsOpen) THEN
        Exit;

     Regel:=LoCaseString (DeleteBackSpaces (Source));

     IF (Source <> '') THEN
        TmpIdx.UserCRC:=UpdateCRC32 ($FFFFFFFF,Regel[1],Length (Source))
     ELSE
         TmpIdx.UserCRC:=$FFFFFFFF;

     TmpIdx.HdrOffset:=Offset;
     {$I-}
     IF (FileSize (IdxFile) <> FilePos (IdxFile)) THEN
        Seek (IdxFile,FileSize (IdxFile));
     BlockWrite (IdxFile,TmpIdx,SizeOf (JAMIDXREC));
     {$I+}
     WriteIndexEntry:=(IOResult = 0);
END;


{--------------------------------------------------------------------------}
{ WriteNewHeaderEntry                                                      }
{                                                                          }
{ Schrijft een JamBase header naar disk.                                   }
{                                                                          }
FUNCTION WriteNewHeaderEntry : BOOLEAN;

VAR TmpSubField : JAMBINSUBFIELD;
    Current     : JAMSUBFIELDPTR;
    IORes       : BYTE;

BEGIN
     WriteNewHeaderEntry:=FALSE;

     IF (NOT IsOpen) THEN
        Exit;

     { Het simpele gedeelte, ofwel het schrijven van de vaste header naar }
     { disk.                                                              }

     {$I-}
     { skip onnodige seek acties }
     IF (FilePos (HdrFile) <> Filesize (HdrFile)) THEN
        Seek (HdrFile,FileSize (HdrFile));
     BlockWrite (HdrFile,Jam_MSG_Header,SizeOf (Jam_MSG_Header));
     {$I+}
     IORes:=IOResult;

     { Schrijf nu alle SubFields naar disk }
     IF (IORes = 0) THEN
     BEGIN
          Current:=JamSubfieldList.GetFirstItem;
          WHILE (Current <> NIL) DO
          BEGIN
               WITH TmpSubField DO
               BEGIN
                    LoID:=Current^.LoID;
                    HiID:=0;              { Reserved }
                    DatLen:=Length (Current^.Buffer);
               END;

               {$I-}
               BlockWrite (HdrFile,TmpSubField,SizeOf (TmpSubField));
               BlockWrite (HdrFile,Current^.Buffer[1],Length (Current^.Buffer));
               UpdateInfoNr (INFO_JamSave_Bytes,SizeOf (TmpSubField)+Length (Current^.Buffer));
               {$I+}
               IORes:=IOResult;

               Current:=JamSubFieldList.GetNextItem;
          END; { while }
     END; { if }

     WriteNewHeaderEntry:=(IORes = 0);

     {## clear subfield list?}
END;


{--------------------------------------------------------------------------}
{ WriteJAMHeader                                                           }
{                                                                          }
{ Schrijft een verse JAM header naar de *.JHR file.                        }
{                                                                          }
FUNCTION WriteJAMHeader : WORD;
BEGIN
     {$I-}
     Seek (HdrFile,0);
     Inc (Jam_JHR_header.modcounter);
     BlockWrite (HdrFile,Jam_JHR_header,SizeOf (Jam_JHR_header));
     {$I+}
     WriteJamHeader:=IOResult;
END;


{--------------------------------------------------------------------------}
{ Import_Flush                                                             }
{                                                                          }
{ This routine is called by the PackBuf code to flush a block of packed    }
{ lines to disk.                                                           }
{                                                                          }
PROCEDURE Import_Flush (VAR Buffer; Count : WORD; APtr : POINTER); FAR;
BEGIN
     BlockWrite (TxtFile,Buffer,Count);
     Inc (JamMsgSize,Count);
     UpdateInfoNr (INFO_JamSave_Bytes,Count);
END;


{--------------------------------------------------------------------------}
{ Import_WriteFooterLine                                                   }
{                                                                          }
{ This routine is called for each line in the footer. SEEN-BY lines are    }
{ stripped as required and the rest is written to disk as-is, including    }
{ the CR.                                                                  }
{                                                                          }
FUNCTION Import_WriteFooterLine (VAR OrigRegel : STRING) : BOOLEAN; FAR;

VAR Regel : STRING;

BEGIN
     Import_WriteFooterLine:=FALSE; { do not abort }

     { SEEN-BY and PATH lines are stored in another way }
     IF (Copy (OrigRegel,1,7) = 'SEEN-BY') OR
        (Copy (OrigRegel,1,6) = #1'PATH:') THEN
        Exit; { drop it }

     Regel:=TransFix_FooterLine (OrigRegel);

     { write this line to disk }
     Import_Flush (Regel[1],Length (Regel),NIL);
END;


{--------------------------------------------------------------------------}
{ JamImport_AttachFile                                                     }
{                                                                          }
{ This routine is called for each file that has been decoded. The path     }
{ points to the file, in the directory that was set for decoded files.     }
{ This function attaches the file to the current message by
{                                                                          }
PROCEDURE JamImport_AttachFile (Path,OriginalName : STRING); FAR;
BEGIN
     LogMessage (liTrivial,'JAM: attaching file');
     {$IFDEF Pre}
     LogExtraMessage ('  Path: "'+Path+'"');
     LogExtraMessage ('  OriginalName: "'+OriginalName+'"');
     {$ENDIF}

     IF (OriginalName <> '') THEN
        Path:=Path+#0+OriginalName;

     AddToSubField (0009,Path);

     { it's required to set the file attach flag }
     Msg.Attr_F:=Msg.Attr_F OR MSGFILE;  { ## not good: modifies original! }
END;


{--------------------------------------------------------------------------}
{ Jam_ImportMessage                                                        }
{                                                                          }
{ Schrijft het bericht zoals zich dat in het interne bericht formaat       }
{ bevindt naar de messagebase.                                             }
{                                                                          }
{ RWI 950506: WriteBodyEntry en StartLine uit de object structuur gehaald  }
{             en het schrijven van het bericht (eventueel gesplitst) hier  }
{             naartoe gehaald, zodat we zekerheden in kunnen bouwen over   }
{             de positie in de swapfile.                                   }
{                                                                          }
PROCEDURE Jam_ImportMessage (Area_Name,Area_Path,DecodePath : STRING; DecodeFiles : BOOLEAN);

VAR SplitBodyLen : LONGINT;

    {----------------------------------------------------------------------}
    { CalcSplitParts                                                       }
    {                                                                      }
    { Probeer het aantal delen te bepalen waarin het bericht gesplitst op  }
    { disk bewaard zal worden. BodySize is 0 or the number returned by     }
    { DecodeEnclosedFiles, which is the number of bytes left in the body   }
    { after all the lines for the files have been removed.                 }
    {                                                                      }
    PROCEDURE CalcSplitParts;

    VAR Lp           : 0..MAX_BODY_PARTS;
        FidoBodyLen  : LONGINT;
        SplitParts_R : REAL;

    BEGIN
         SplitParts:=0;

         IF (Config.MaxJamMsgLen = 0) THEN
         BEGIN
              { no splitting needed }
              SplitBodyLen:=MAXLONGINT; { no limit }
              Exit;
         END;

         SplitBodyLen:=Config.MaxJamMsgLen;

         { count the total size of all parts, exclusive the attachments  }
         { (only when DecodeFiles is TRUE) and excluding the MIME multi- }
         { part header lines.                                            }

         FidoBodyLen:=MsgsCalcBodyLen ({IncludeAttachments}NOT DecodeFiles);

         {## verify that we have to add the header and footer lines}
         IF (Msg.HeaderTop_F <> NIL) THEN
            Dec (SplitBodyLen,Msg.HeaderTop_F^.TotalRegelLength);

         IF (Msg.CopiedHeadersTop_F <> NIL) THEN
            Dec (SplitBodyLen,Msg.CopiedHeadersTop_F^.TotalRegelLength);

         IF (Msg.FooterTop_F <> NIL) THEN
            Dec (SplitBodyLen,Msg.FooterTop_F^.TotalRegelLength);

         { calculate the number of split parts we will get }

         SplitParts_R:=FidoBodyLen / SplitBodyLen;
         SplitParts:=Trunc (SplitParts_R);
         IF (SplitParts < SplitParts_R) THEN
            Inc (SplitParts);
    END;


    {----------------------------------------------------------------------}
    { CreateMessage                                                        }
    {                                                                      }
    { This routine is called to initiate a new message. The message is     }
    { added to the structure of the base and the header is written.        }
    {                                                                      }
    PROCEDURE CreateMessage;
    BEGIN
         { zorgt dat we aan het einde van de base toevoegen }
         Seek (TxtFile,FileSize (TxtFile));

         { used when the header is created }
         JamMsgSize:=0;

         { zorg voor een schone sub field lijst }
         JamSubFieldList.Clear;
         TotalSubFieldSize:=0;

         { can now add attached files to list }
    END;


    {----------------------------------------------------------------------}
    { CompleteMessage                                                      }
    {                                                                      }
    { This routine is called to finialise a message. The administration of }
    { the base is finished and the message is added to the index.          }
    {                                                                      }
    PROCEDURE CompleteMessage;
    BEGIN
         { add the tear line, Origin and Via kludges to the body }
         MsgsForEach (Msg.FooterTop_F,Import_WriteFooterLine);

         JamMsgHeaderOffset:=FileSize (HdrFile);

         { schrijf een index entry naar disk }
         IF (NOT WriteIndexEntry (Msg.Stored_ToUser,JamMsgHeaderOffset)) THEN
         BEGIN
              LogMessage (liFatal,'[JAM] Unable to update index for '+AreaName);
              Exit;
         END;

         { bouw de bericht headers in het geheugen }
         FillSubFields; { kan swapfile positie wijzigen }
         FillMsgHeader;

         { write the header and sub-fields to disk }
         IF (NOT WriteNewHeaderEntry) THEN
         BEGIN
              LogMessage (liFatal,'[JAM] Unable to write header for '+AreaName);
              Exit;
         END;

         Inc (Jam_JHR_Header.ActiveMsgs);

         { update de fixed header }
         WriteJamHeader;

         UpdateInfoNr (INFO_JamSave_Msgs,1);

         IF (Msg.Ready_F IN [Netmail,Local_Netmail]) THEN
            UpdateInfoNr (INFO_JamSave_Net,1)
         ELSE
             UpdateInfoNr (INFO_JamSave_Echo,1);
    END;

{ Jam_ImportMessage }

VAR PosRec     : ForEach_PosRecord;
    AllDone    : BOOLEAN;
    SkipCreate : BOOLEAN;

BEGIN
     { --- the Init part }

     IF (NOT OpenBase (FALSE,Area_Name,Area_Path)) THEN
     BEGIN
          LogMessage (liFatal,'[JAM] Unable to access '+AreaName);
          Exit;
     END;

     IF (NOT LockBase) THEN
     BEGIN
          LogMessage (liFatal,'[JAM] Timeout on file lock request for '+AreaPath);
          {CloseBase... ??}
          Exit;
     END;

     { zorg dat we verse informatie hebben }
     ReadJamHeader;

     UpdateWriteFile (Area_Path,0);

     { --- the Import part }

     SkipCreate:=FALSE;

     IF DecodeFiles THEN
     BEGIN
          { need to start creating the first message, even if we don't  }
          { know the split part size and all yet, because we need to be }
          { able to attach decoded files. The First flag prevents a new }
          { message from being created when we go in the repeat loop    }
          { below.                                                      }
          SkipCreate:=TRUE;
          CreateMessage;

          DecodeAttachedFiles (DecodePath,JamImport_AttachFile);
     END;

     { decode files has now marked all parts that are attachments  }
     { if DecodeFiles is false, then CalcSplitParts will count all }
     { parts, otherwise just the ones not marked Attachment.       }
     CalcSplitParts;

     MsgsLimited_Init (PosRec,{IncludeAttachments=}(NOT DecodeFiles));

     SplitCurrent:=0;
     REPEAT
           Inc (SplitCurrent);

           IF PackBuf_Init (Import_Flush,lttCR,NIL) THEN
           BEGIN
                IF SkipCreate THEN
                   SkipCreate:=FALSE
                ELSE
                    CreateMessage;

                { write body }
                PackBuf_ReplaceNul ('!');
                AllDone:=MsgsLimited_ForEach (PosRec,SplitBodyLen,PackBuf_AddLine);

                PackBuf_Done;

                CompleteMessage;

           END ELSE
               AllDone:=TRUE;

     UNTIL AllDone;

     { --- the Complete part }

     JamSubFieldList.Clear; { prevent memory clutter }

     UnlockBase;
END;


{--------------------------------------------------------------------------}
{ ReadHeader                                                               }
{                                                                          }
{ Leest een JAM base header + alle subfields in het geheugen. De subfields }
{ die gebruikt kunnen worden, worden omgezet in het interne formaat.       }
{                                                                          }
FUNCTION ReadHeaderEntry : BOOLEAN;

VAR IORes : BYTE;

BEGIN
     ReadHeaderEntry:=FALSE;

     { controleer of de bestanden wel open zijn }
     IF (NOT IsOpen) THEN
        Exit;

     { we beginnen simpel, lees de vaste header in }
     JamMsgHeaderOffset:=FilePos (HdrFile);

     {$I-} BlockRead (HdrFile,Jam_MSG_Header,SizeOf (Jam_MSG_Header)); {$I+}
     IORes:=IOResult;

     IF (IORes <> 0) THEN
        Exit; { false }

     { controleer of de header wel klopt }
     IF (Jam_MSG_Header.Signature <> HeaderSig) THEN
        Exit; { false }

     ReadHeaderEntry:=TRUE;
END;


{---------------------------------------------------------------------------}
{ ReadJAMSubFields                                                          }
{                                                                           }
{ Leest de header subfields in en vertaald de gegevens naar het interne     }
{ *.MSG formaat.                                                            }
{                                                                           }
FUNCTION ReadJAMSubFields (Import : BOOLEAN) : BOOLEAN;

VAR SubFieldLen : LONGINT;
    SubFieldBin : JAMBINSUBFIELD;
    Ignore,
    Buffer      : STRING;
    IORes       : BYTE;
    HadFromAddr : BOOLEAN;

BEGIN
     ReadJAMSubFields:=TRUE;
     Ignore:='';
     SubFieldLen:=Jam_MSG_Header.SubFieldLen;
     HadFromAddr:=FALSE;

     WHILE (SubFieldLen > SizeOf (JAMBINSUBFIELD)) DO
     BEGIN
          { Lees het binaire header gedeelte gevolgt door de buffer }
          {$I-} BlockRead (HdrFile,SubFieldBin,SizeOf (JAMBINSUBFIELD));

          { Controlleer of SubfieldLen wel een geldige waarde heeft ! }
          { Als die echter troep bevat ging hij mooi door de buffer   }
          { heen, met als gevolg dat het programma vastliep !         }

          IF (SubFieldBin.DatLen > 255) THEN
          BEGIN
               LogMessage (liFatal,'[JAM] Damaged (too long) message header found in '+AreaName);
               ReadJAMSubFields:=FALSE;
               Break;
          END;

          BlockRead (HdrFile,Buffer[1],SubFieldBin.DatLen); {$I+}
          Buffer[0]:=Char (SubFieldBin.DatLen);
          IORes:=IOResult;
          IF (IORes <> 0) THEN
          BEGIN
               LogDiskIOError (IORes,'[JAM] Fatal error reading subfield in '+AreaName);
               ReadJAMSubFields:=FALSE;
               Exit;
          END;

          CASE SubFieldBIN.LoID OF
{ From Adres } 0000 :
                   BEGIN
                        FidoSplit (Buffer,Msg.FromAddr_F);
                        HadFromAddr:=TRUE;
                   END;

{ To   Adres } 0001 : FidoSplit (Buffer,Msg.Stored_ToAddr);
{ From User  } 0002 : Msg.FromUser_F:=Buffer;
{ To   User  } 0003 : Msg.Stored_ToUser:=Buffer;
          END; { case }

          { RWI 941214: waar nodig #13 toegevoegd. Stond nog nergens,   }
          {             waardoor de tear & origin aan elkaar zaten etc. }
          IF Import THEN
             CASE SubFieldBIN.LoID OF
   { MsgID }      0004 :
                      BEGIN
                           FidoAddLineToMessage (#1'MSGID: '+Buffer+#13,Ignore);

                           { RWI 960821: added support for missing 0000 }
                           IF (NOT HadFromAddr) AND (Buffer[1] IN ['0'..'9']) AND (Pos (' ',Buffer) > 0) THEN
                              FidoSplit (Copy (Buffer,1,Pos (' ',Buffer)-1),Msg.FromAddr_F);
                      END;

   { Reply }      0005 : FidoAddLineToMessage (#1'REPLY: '+Buffer+#13,Ignore);
 { Subject }      0006 : Msg.Subj_F:=Buffer;
     { PID }      0007 : FidoAddLineToMessage (#1'PID: '+Buffer+#13,Ignore);
   { Trace }      0008 : FidoAddLineToMessage (#1'Via: '+Buffer+#13,Ignore);
{ Enclosed }      0009 :
                      BEGIN
                           Msg.Subj_F:=Buffer;
                           Msg.Attr_F:=Msg.Attr_F OR MSGFILE;
                      END;

     { FTS }      2000 : FidoAddLineToMessage (#1+Buffer+#13,Ignore);
  { SeenBy }      2001 : FidoAddLineToMessage ('SEEN-BY: '+Buffer+#13,Ignore);
    { Path }      2002 : FidoAddLineToMessage (#1'PATH: '+Buffer+#13,Ignore);
   { Flags }      2003 : FidoAddLineToMessage (#1'FLAGS: '+Buffer+#13,Ignore);
             END; { case, if }

             Dec (SubFieldLen,SubFieldBin.DatLen+SizeOf (JAMBINSUBFIELD));
     END; { while }

     IF (SubFieldLen > 0) THEN
     BEGIN
          {
          LogMessage ('[JAM] Trying to work around SubFields length error in '+AreaName+
                      ' (off by '+Longint2String (SubFieldLen)+')');
          }
          Seek (HdrFile,FilePos (HdrFile)+SubFieldLen);
     END;
END;


{---------------------------------------------------------------------------}
{ ReadJAMSubField0003                                                       }
{                                                                           }
{ Leest de header subfields op zoek naar nummer 0003. Deze is bevat de      }
{ to-user en wordt gebruikt in de index.                                    }
{                                                                           }
FUNCTION ReadJAMSubField0003 : STRING;

VAR SubFieldLen : LONGINT;
    SubFieldBin : JAMBINSUBFIELD;
    Buffer      : STRING;

BEGIN
     ReadJAMSubField0003:='';

     SubFieldLen:=Jam_MSG_Header.SubFieldLen;

     WHILE (SubFieldLen > SizeOf (JAMBINSUBFIELD)) DO
     BEGIN
          { Lees het binaire header gedeelte gevolgt door de buffer }
          {$I-} BlockRead (HdrFile,SubFieldBin,SizeOf (JAMBINSUBFIELD)); {$I+}
          IF (IOResult <> 0) THEN
             Exit;

          IF (SubFieldBin.DatLen > 255) THEN
             Exit;

          BlockRead (HdrFile,Buffer[1],SubFieldBin.DatLen); {$I+}
          Buffer[0]:=Char (SubFieldBin.DatLen);
          IF (IOResult <> 0) THEN
             Exit;

          IF (SubFieldBIN.LoID = 0003) THEN
          BEGIN
               { gevonden! }
               ReadJAMSubField0003:=Buffer;
               Exit;
          END;

          Dec (SubFieldLen,SubFieldBin.DatLen+SizeOf (JAMBINSUBFIELD));
     END; { while }
END;


{--------------------------------------------------------------------------}
{ Jam_ScanArea                                                             }
{                                                                          }
{ Scant een JAM area op lokale berichten die nog niet verstuurd zijn.      }
{                                                                          }
PROCEDURE Jam_ScanArea (AreaRecNr            : AreaBaseRecordNrType;
                        AreaData             : AreaBaseRecord;
                        IsPrimaryNetmailArea : BOOLEAN;
                        IsBadArea            : BOOLEAN);

    {----------------------------------------------------------------------}
    { UpdateMsgAttrs                                                       }
    {                                                                      }
    { This routine reads the message header from disk and updates the      }
    { Attributes field according to the Scan Result and writes the header  }
    { back to disk. Base needs to be locked before the calls.              }
    {                                                                      }
    PROCEDURE UpdateMsgAttrs (OldHdrPos : LONGINT; Result : ScanDeliverResultType);

    VAR IORes : BYTE;
        Hdr   : JAMHDR;

    BEGIN
         {$I-}
         Seek (HdrFile,OldHdrPos);
         BlockRead (HdrFile,Hdr,SizeOf (JAMHDR));
         {$I+}
         IORes:=IOResult;

         IF (IORes <> 0) THEN
         BEGIN
              LogDiskIOError (IORes,'[JamScan] Failed to read attributes');
              Exit;
         END;

         {## must remove UNS flag from FLAGS kludge as well }
         {   maybe even re-write the entire FLAGS kludge?   }

         IF (Result = sdSent) THEN
            Hdr.Attribute:=Hdr.Attribute OR JMSG_SENT;

         IF (Result = sdReceived) THEN
            Hdr.Attribute:=Hdr.Attribute OR JMSG_READ;

         IF (Result = sdKill) THEN
            Hdr.Attribute:=Hdr.Attribute OR JMSG_DELETED;

         IF (Result = sdOrphan) THEN
            Hdr.Attribute:=Hdr.Attribute OR JMSG_ORPHAN;

         {$I-}
         Seek (HdrFile,OldHdrPos);
         BlockWrite (HdrFile,Hdr,SizeOf (JAMHDR));
         {$I+}
         IORes:=IOResult;
         IF (IORes <> 0) THEN
            LogDiskIOError (IORes,'[JamScan] Failed to write attributes');
    END;

{Jam_ScanArea}

VAR BodyFile    : FBufferType;
    DatumTijd   : DateTime;
    BytesToRead : LONGINT;
    Regel,
    LastRegel   : STRING;
    RealLen     : BYTE;
    TrashCount  : WORD;
    IORes       : BYTE;
    OldIdxPos   : LONGINT;
    OldHdrPos   : LONGINT;
    PrevTouch   : BYTE;
    FirstExport : BOOLEAN;
    Result      : ScanDeliverResultType;

LABEL GaVerder;

BEGIN
     FirstExport:=(NOT IsBadArea);

     { at export: this message is not from a user, but from a msgbase }
     UserDataRecNr:=NILRecordNr;

     IF (NOT OpenBase (FALSE,AreaData.AreaName_F,AreaData.FidoMsgPath)) THEN
     BEGIN
          LogMessage (liFatal,'[JAM] Unable to access '+AreaData.AreaName_F);
          Exit;
     END;

     ReadJamHeader;     { zorg dat we verse informatie hebben }
     ImportMsgs:=TRUE;  { zorg dat we alles lezen }

     { zoekt in de index en geeft de pointer naar het eerste bericht }
     IF (NOT GetFirstMessage) THEN
     BEGIN
          { er zijn geen berichten aanwezig }
          CloseBase;
          Exit;
     END;

     REPEAT
           { Lees de bericht header en subfields in het geheugen }
           IF (NOT ReadHeaderEntry) THEN
           BEGIN
                LogMessage (liFatal,'[JAM] Error reading message header for '+AreaName+' in msg '+
                            Word2String (Currentmessage)+', possible index failure?');

                { einde area }
                CloseBase;
                Exit;
           END;

           IF ((Jam_MSG_header.Attribute AND JMSG_DELETED) <> 0) THEN
              GOTO GaVerder;

           IF (NOT IsBadArea) THEN
           BEGIN
                { Controleer of het bericht                    }
                {   a) de local vlag heeft                     }
                {   b) niet de sent vlag                       }
                {   c) niet de deleted vlag heeft              }
                {   d) niet de Hold vlag heeft (RWI 950531)    }
                { want ga dan verder met het volgende bericht  }
                IF ((Jam_MSG_header.Attribute AND JMSG_LOCAL) = 0) OR
                   ((Jam_MSG_header.Attribute AND JMSG_SENT) <> 0) OR
                   ((Jam_MSG_header.Attribute AND JMSG_HOLD) <> 0) OR
                   ((Jam_MSG_header.Attribute AND JMSG_LOCKED) <> 0) OR { RAWI 970804 }
                   ((Jam_MSG_header.Attribute AND JMSG_FILEREQUEST) <> 0) OR
                   ((Jam_MSG_Header.Attribute AND JMSG_READ) <> 0) OR
                   ((Jam_MSG_Header.Attribute AND JMSG_ORPHAN) <> 0)
                THEN
                    GOTO GaVerder;
           END;

           { lees alleen de header in }

           MsgsEmpty;

           IF (NOT IsBadArea) THEN
           BEGIN
                CASE AreaData.AreaType OF
                     Area_Netmail:
                         Msg.Ready_F:=Local_Netmail;

                     Area_EMail:
                         BEGIN
                              { RWI 970223: now using AreaData Origin AKA for From }
                              Msg.FromAddr_F:=Config.NodeNrs[AreaData.OriginAKA];
                              Msg.Ready_F:=Local_Netmail;
                         END;

                     ELSE BEGIN
                          Msg.Ready_F:=Local_Echomail;
                          { Stop de AREA: kludge in de eerste regel van het bericht }
                          Msg.Area_F:=AreaData.AreaName_F;
                          {RAWI 971012: added by PKT export
                          MsgsAddLineTo (Header_F,'AREA:'+Msg.Area_F);
                          }
                     END;
                END; { case }
           END;

           { als er subfields waren, lees die dan ook in }
           IF (Jam_MSG_Header.SubFieldLen > 0) THEN
              ReadJAMSubFields (ImportMsgs);

           { Voeg INTL, TOPT en FMPT regels toe }
           IF (AreaData.AreaType = Area_EMail) THEN
           BEGIN
                IF (Msg.Stored_ToUser= '') THEN
                   { expecting To: on first line }
                   Msg.Stored_ToUser:=Config.GatewayUser
                ELSE
                    IF (UpCaseString (Msg.Stored_ToUser) <> Config.GatewayUser) THEN
                    BEGIN
                         IF (Pos ('@',Msg.Stored_ToUser) = 0) AND (Pos ('!',Msg.Stored_ToUser) = 0) THEN
                         BEGIN
                              IF Config.LogDebug THEN
                                 LogMessage (liFatal,'JAM['+AreaData.AreaName_F+'] Msg '+
                                                     Longint2String (Jam_Msg_Header.MsgNum)+
                                                     ': No e-mail address in To: ("'+Msg.Stored_ToUser+'")');
                              GOTO GaVerder;  { geen e-mail! }
                         END;

                         IF Config.LogExportedMsgs THEN
                         BEGIN
                              IF FirstExport THEN
                              BEGIN
                                   FirstExport:=FALSE;
                                   LogMessage (liTrivial,'Exporting from '+AreaData.AreaName_F+' (JAM)');
                              END;

                              LogMessage (liTrivial,'  Exporting e-mail for '+Msg.Stored_ToUser);
                         END;

                         MsgsAddFirstLineTo (Body,'To: '+Msg.Stored_ToUser);
                         Msg.Stored_ToUser:=Config.GatewayUser;
                    END;
                    { else assume To: in body }

                { zoek het bijpassende system node nummer voor in de To: }
                FidoMatch (Msg.FromAddr_F,Msg.Stored_ToAddr);
                Msg.Ready_F:=Local_Netmail;
           END;

           { converteer de Jam_Header naar de interne structuur }
           Msg.Attr_F:=AttrJam2Ftn;
           Msg.ExtAttr_F:=AttrJam2ExtFtn;

           { Als dit een netmail bericht is en we draaien in frontdoor mode }
           { mag het bericht alleen verstuurd worden als het a) voor ons    }
           { bestemd is, en b) voor postmaster is !                         }

           IF (NOT IsBadArea) AND
              (Msg.Ready_F = Local_Netmail) AND { RWI 941102: toegevoegd, anders krijg echomail geen sent flag }
              (NOT FidoCheckNetmail (IsPrimaryNetmailArea,Msg.Stored_ToUser,Msg.Stored_ToAddr))
           THEN
               { body file is al dicht... }
               GOTO GaVerder; { netmail voor FD. Bye! }

           { read the message }

           { converteer de JAM Header naar de fido stijl header }
           UnixToDos (Jam_MSG_header.DateWritten,DatumTijd);

           { stel de datum in }
           WITH DatumTijd DO
                Msg.Date_F:=FidoTime2Str (Day,Month,Year,Hour,Min,Sec);

           Msg.Cost_F:=jam_MSG_header.Cost;

           { open de invoer file }
           IF (NOT FBufferOpen (BodyFile,AreaPath+EXT_TXTFILE,10000,0)) THEN
           BEGIN
                LogMessage (liFatal,'[JAM] Unable to open area '+AreaName);
                FBufferClose (BodyFile);
                CloseBase; { RWI 941102 }
                Exit;
           END;

           IF (NOT FBSeek (BodyFile,Jam_MSG_header.TxtOffset)) THEN
           BEGIN
                LogMessage (liFatal,'[JAM] Error seeking position '+Longint2String (Jam_MSG_header.TxtOffset)+
                            ' in '+AreaPath+EXT_TXTFILE);
                FBufferClose (BodyFile);
                CloseBase;
                Exit;
           END;

           { lees het bericht regel voor regel in }
           BytesToRead:=Jam_MSG_header.TxtLen;

           { --- Init boolean variabelen }
           Found_SeenBy:=FALSE;
           Found_Path:=FALSE;
           Found_Origin:=FALSE;
           Found_Tear:=FALSE;

           { RWI 950120: BytesToRead controle weer ingebouwd en }
           {             FBReadLnCRTell gemaakt voor correcte   }
           {             telling.                               }
           WHILE (BytesToRead > 0) AND
                 FBReadLnCRTell (BodyFile,Regel,TrashCount) AND
                 (Regel <> #0) DO
           BEGIN
                { RWI 960715: Als de body van een JAM bericht eindigt op }
                {             een #0 dan gaat alles goed. Maar als ie op }
                {             een LF eindigt dan wordt die pas bij het   }
                {             ophalen van de volgende regel als Trash    }
                {             terug gegeven, samen met de eerste regel   }
                {             van het volgende bericht.                  }
                {             De volgende control lost dit netjes op en  }
                {             voorkomt uitzonderingssituaties.           }
                IF (BytesToRead = 1) AND (TrashCount > 0) THEN
                   Break; { reached end of message }

                Dec (BytesToRead,Length (Regel)+TrashCount);

                { filter alle soft CR's eruit }
                WHILE (Pos (#$8D,Regel) > 0) DO
                      Delete (Regel,Pos (#$8D,Regel),1);

                { FidoAddLineToMessage gaat er vanuit dat de nodige }
                { cr,lf enzo al in Regel zitten. Dat hebben we dus  }
                { precies nodig om regels langer dan 255 tekens toe }
                { te kunnen voegen...                               }
                FidoAddLineToMessage (Regel,LastRegel);
           END; { while }

           { RWI 960821: de laatste CR wordt soms gestript in JAM bases, }
           {             en dan wordt onze Via kludge aan de laatste     }
           {             kludge vastgeplakt, dus we zorgen er hier voor  }
           {             dat de laatste kludge een #13 had.              }
           IF (LastRegel[Length (LastRegel)] <> #13) AND (PrevKludgeID <> klNone) THEN
              MsgsAddLineToNoEOL (Footer_F,#13);

           FidoAddLastLine (LastRegel);

           { de body hebben we nu niet meer nodig... }
           FBufferClose (BodyFile);

           { WTRBAD_AREA kludge sets Msg.BadAreaRecNr }
           IF IsBadArea AND (Msg.BadAreaRecNr = NILRecordNr) THEN
              GOTO GaVerder;

           { Zorg dat er een tearline wordt toegevoegd, een origin line }
           { zodat we een 'echt' fido bericht krijgen.                  }
           IF (AreaData.AreaType = Area_Echo) THEN
              FidoFinishEchomailExport (AreaData);

           { -- Exporteer het bericht    }

           { nu wordt het tricky, want MsgsExport kan een JAM base     }
           { gebruiken. Bijvoorbeeld als we een bericht terug krijgen, }
           { maar ook als de listserver zijn werk doet..               }
           { DUS, moeten we van alles doen om er zeker van te zijn dat }
           { we straks weer verder kunnen. Het sluiten van de base     }
           { gebeurt vanzelf als het nodig is. We moeten dus alleen    }
           { positionele gegevens bijhouden.                           }

           PrevTouch:=TouchCounter;
           OldIdxPos:=FilePos (IdxFile);
           OldHdrPos:=CurrentHeader;

           UpdateInfoNr (INFO_JamScan_Msgs,1);

           IF FirstExport THEN
           BEGIN
                FirstExport:=FALSE;
                LogMessage (liTrivial,'Exporting from '+AreaData.AreaName_F+' (JAM)');
           END;

           IF (Msg.Ready_F = Local_Netmail) THEN
           BEGIN
                UpdateInfoNr (INFO_JamScan_Net,1);

                IF Config.LogExportedMsgs AND (AreaData.AreaType <> Area_Email) THEN
                   LogMessage (liTrivial,'  Exporting netmail for "'+Msg.Stored_ToUser+'"%'+Fido2Str (Msg.Stored_ToAddr));
           END ELSE
           BEGIN
                UpdateInfoNr (INFO_JamScan_Echo,1);

                {### stats}
                {
                UpdateAreaStats (GetAreaBaseRecordNrByAreaName_F (AreaData.AreaName_F),Msg.MsgSize);
                UpdateUserStats (NILRecordNr=Local,EchoFrom,Msg.MsgSize); 
                }

                IF IsBadArea THEN
                   LogMessage (liGeneral,'  Re-tossing BAD message to '+Msg.Area_F)
                ELSE
                    IF Config.LogExportedMsgs THEN
                       LogMessage (liTrivial,'  Exporting echomail for "'+Msg.Stored_ToUser+'"');
           END;

           UpdateInfoNr (INFO_JamScan_Bytes,MsgsCalcMessageSize);

           Result:=Scan_DeliverMessage (AreaRecNr,IsBadArea);

           IF (TouchCounter <> PrevTouch) THEN
           BEGIN
                { Er is iets gebeurt met de JAM-base object instantie. }
                { Nu moeten we de boel herstellen.                     }

                IF (NOT OpenBase (FALSE,AreaData.AreaName_F,AreaData.FidoMsgPath)) THEN
                BEGIN
                     LogMessage (liFatal,'[JAM] Unable to re-access '+AreaData.AreaName_F);
                     Exit;
                END;

                ReadJamHeader;     { zorg dat we verse informatie hebben }
                ImportMsgs:=TRUE;  { zorg dat we alles lezen }

                { herstel nu de file pointer in de index file, want die }
                { gebruikt GetNextMessage.                              }
                Seek (IdxFile,OldIdxPos);
           END;

           { update message Attributes according to Result }
           IF (NOT LockBase) THEN
              LogMessage (liFatal,'[JAM.SCANAREA] Cannot lock area to update Flags')
           ELSE BEGIN
                UpdateMsgAttrs (OldHdrPos,Result);
                UnlockBase;
           END;

GaVerder:

     UNTIL (NOT GetNextMessage);

     { Einde area }
     CloseBase;
     MsgsEmpty;
END;


{--------------------------------------------------------------------------}
{ Synchonize                                                               }
{                                                                          }
{ Zoekt in een file naar een bepaalde string, om zo eventuele goede        }
{ headers te vinden die niet goed doorverwerzen worden.                    }
{                                                                          }
FUNCTION Synchronize (VAR Invoer : FILE; VAR ZoekString; Length : BYTE) : BOOLEAN;

VAR Teken,NextTeken : CHAR;
    SearchVar       : JAMBuf ABSOLUTE ZoekString;
    Teller          : BYTE;
    IORes           : BYTE;

BEGIN
     Synchronize:=FALSE;
     WHILE (NOT Eof (Invoer)) DO
     BEGIN
          {$I-} BlockRead (Invoer,Teken,1); {$I+} IORes:=IOResult;
          IF (IORes <> 0) THEN
          BEGIN
               LogDiskIOError (IORes,'[JAM] Error while synchonizing on header file in '+AreaName);
               Exit;
          END;

          IF (Teken = SearchVar[0]) THEN
          BEGIN
               FOR Teller:=1 TO (Length-1) DO
               BEGIN
                    {$I-} BlockRead (Invoer,Teken,1); {$I+} IORes:=IOResult;
                    IF (Teken <> SearchVar[Teller]) THEN
                       Break; { uit de for }
               END; { for }

               IF (Teller = (Length-1)) THEN
               BEGIN
                    Seek (Invoer,FilePos (Invoer)-Length);
                    Synchronize:=TRUE;
                    Exit;
               END;
          END; { if }
     END; { while }
END;


{--------------------------------------------------------------------------}
{ Jam_ReIndexArea                                                          }
{                                                                          }
{ Creert een nieuwe index voor een JAM area.                               }
{                                                                          }
PROCEDURE Jam_ReIndexArea (AreaData : AreaBaseRecord);

VAR NextEntry : LONGINT;
    First     : BOOLEAN;
    IORes     : BYTE;
    TmpIdx    : JAMIDXREC;

LABEL GaVerder;

BEGIN
     { zorg ervoor dat de base open is }
     ImportMsgs:=TRUE; { zorg dat we alleen header lezen }

     IF (NOT OpenBase (TRUE,AreaData.AreaName_F,AreaData.FidoMsgPath)) THEN
     BEGIN
          LogMessage (liFatal,'[JAM] Unable to access '+AreaData.AreaName_F);
          Exit;
     END;

     { zorg dat andere programma niet meer kunnen }
     IF (NOT LockBase) THEN
     BEGIN
          LogMessage (liFatal,'[JAM] Area locked tight ('+AreaName+'); reindex failed');
          CloseBase;
          Exit;
     END;

     { overschrijf de indexfile }
     {$I-} ReWrite (IdxFile,1); {$I+} IORes:=IOResult;
     IF (IORes <> 0) THEN
     BEGIN
          LogDiskIOError (IORes,'[JAM] Unable to recreate index file for '+AreaName);
          CloseBase;
          Exit;
     END;

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

     { zorg voor up-to-date informatie }
     ReadJAMHeader;

     First:=TRUE;

     Status.DezeArea:=0;
     Status.DezeTodo:=0; {Jam_MSG_Header.ActiveMsgs;}

     WHILE (NOT Eof (HdrFile)) DO
     BEGIN
          IF (NOT ReadHeaderEntry) THEN
          BEGIN
               { probeer het volgende intacte frame te vinden }
               IF (NOT Synchronize (HdrFile,HeaderSig,SizeOf (HeaderSig))) THEN
               BEGIN
                    LogMessage (liFatal,'[JAM] Error reading message header in '+Areaname);
                    CloseBase; { RWI 970204 }
                    Exit;
               END;

               Continue; { resynchronized }
          END;

          { Pas de base message teller aan aan het eerste bericht  }
          { Deze teller bevat tenslotte het nummer van het laagste }
          { bericht.                                               }
          IF First THEN
          BEGIN
               JAM_JHR_header.BaseMsgNum:=JAM_MSG_Header.MsgNum;
               First:=FALSE;
          END;

          { Voeg een bericht toe als het niet gedelete is       }
          { En het een geldig nummer heeft, ergo .. groter dan  }
          { het base number.                                    }
          {
          IF ((Jam_MSG_header.Attribute AND JMSG_DELETED) <> 0) THEN
             GOTO GaVerder;
          }

          {
          IF ((Jam_MSG_header.MsgNum < JAM_JHR_header.BaseMsgNum)) THEN
             Continue;
          }

          { Als het bericht nummer groter is dan het vorige bericht nummer }
          { vul de header aan met lege records.                            }
          NextEntry:=(Jam_Msg_Header.MsgNum-Jam_JHR_header.BaseMsgNum)*SizeOf (JAMIDXREC);

          IF (NextEntry < FileSize (IdxFile)) THEN
          BEGIN
               { Een bericht dat niet in de goede volgorde staat .. }
               { Skippen... dat wordt bij de volgende purge wel     }
               { meegenomen.                                        }
               { RWI 941115: loggen toegevoegd }
               LogMessage (liFatal,'[JAM] Wrong order for message '+Longint2String (Jam_Msg_header.MsgNum)+
                                   '; Skipping for now;');
               LogExtraMessage ('Please renumber '+AreaName);
               GOTO GaVerder;
          END;

          IF (NextEntry-FileSize (IdxFile) > 8192) THEN
             LogMessage (liGeneral,'[JAM] Advise: renumber '+AreaName+' to save space');

          { Vul de index aan met blanke entry's voor alle bericht nummers }
          { die blijkbaar niet voorkomen.                                 }
          { RWI 941115: aangepast zodat er niet meer gekeken wordt naar   }
          {             de grootte van het gat.                           }
          WHILE (NextEntry > FileSize (IdxFile)) DO
                IF (NOT WriteIndexEntry ('',$FFFFFFFF)) THEN
                BEGIN
                     LogMessage (liFatal,'[JAM] Unable to append to index file '+AreaName);
                     Exit;
                END; { if, while }

          { voeg een index entry toe }
          IF (NOT WriteIndexEntry (ReadJamSubField0003,JamMsgHeaderOffset)) THEN
          BEGIN
               LogMessage (liFatal,'[JAM] Unable to append to index file '+AreaName);
               Exit;
          END;

          Inc (Status.DezeArea);
          IF ((Status.DezeArea MOD 24) = 1) THEN
          BEGIN
               UtilUpdateProgress;
               Slice_Now;
          END;

     GaVerder:

          { subfields blok overslaan, klaar zetten voor volgende header }
          Seek (HdrFile,JamMsgHeaderOffset+SizeOf (Jam_MSG_Header)+Jam_MSG_Header.SubFieldLen);

     END; { while }

     { RWI 941115: update de header, want daarin staat het eventuele }
     {             nieuwe BaseMsgNum.                                }
     IF (NOT First) THEN
        WriteJAMHeader;

     { geef de base weer vrij voor gebruik }
     UnlockBase;
     CloseBase;
END;


{--------------------------------------------------------------------------}
{ Jam_PackArea                                                             }
{                                                                          }
{ Verwijderd oude en/of deleted berichten uit de base. Dit door een nieuwe }
{ header en body te creeren en berichten te kopieren.                      }
{                                                                          }
{ RWI 960928: aangepast omdat berichten verwijderd werden bij >max.        }
{             Hierbij worden de 128 oudste berichten tijdens een run       }
{             bewaards voor de volgende run. Bij gelijke data worden de    }
{             eerst voorkomende verwijderd.                                }
{                                                                          }
PROCEDURE Jam_PackArea (AreaData : AreaBaseRecord);

CONST MaxPacks = 128;

VAR PackDates   : ARRAY[1..MaxPacks] OF LONGINT;
    PackFPos    : ARRAY[1..MaxPacks] OF LONGINT;
    IdxRec      : JAMIDXREC;
    OldSize,
    NewSize     : LONGINT;

    {----------------------------------------------------------------------}
    { AddPack                                                              }
    {                                                                      }
    { Voeg een entry toe aan de het Pack array dat straks gebruikt wordt   }
    { om extra entries af te schieten, mocht dat nodig zijn. Deze wordt    }
    { ingevoegd, mocht ie niet jonger dan de jongste zijn.                 }
    {                                                                      }
    PROCEDURE AddPack (FPos,Date : LONGINT);

    VAR Lp,Lp2 : 1..MaxPacks;

    BEGIN
         FOR Lp:=1 TO MaxPacks DO
             IF (Date < PackDates[Lp]) THEN
             BEGIN
                  FOR Lp2:=MaxPacks-1 DOWNTO Lp DO
                  BEGIN
                       PackDates[Lp2+1]:=PackDates[Lp2];
                       PackFPos[Lp2+1]:=PackFPos[Lp2];
                  END;

                  PackDates[Lp]:=Date;
                  PackFPos[Lp]:=FPos;

                  Exit;
             END;
    END;

    {----------------------------------------------------------------------}
    { ReadIndexEntry                                                       }
    {                                                                      }
    { Net als GetNextMessage, maar dan lokaal zodat de hele index entry    }
    { op de stack bewaard kan worden en naar de nieuwe index geschreven    }
    { kan worden.                                                          }
    {                                                                      }
    FUNCTION ReadIndexEntry : BOOLEAN;

    VAR IORes : BYTE;

    BEGIN
         ReadIndexEntry:=FALSE; { assume failure }

         WHILE (NOT Eof (IdxFile)) DO
         BEGIN
              {$I-} BlockRead (IdxFile,IdxRec,SizeOf (IdxRec)); {$I+} IORes:=IOResult;
              IF (IORes <> 0) THEN
                 Exit; { read past end of file? }

              { Lege index posities hebben een positie met de waarde }
              { $FFFFFFFF deze skippen we.                           }
              IF (IdxRec.HdrOffSet <> $FFFFFFFF) THEN
              BEGIN
                   {$I-} Seek (HdrFile,IdxRec.hdrOffset); {$I+}
                   IORes:=IOResult; { negeer het resultaat }

                   CurrentMessage:=(FilePos (IdxFile) DIV SizeOf (IdxRec))+Jam_JHR_Header.basemsgnum;
                   CurrentHeader:=IdxRec.HdrOffset;

                   ReadIndexEntry:=TRUE;
                   Exit; { uit de while }
              END;
         END; { while }
    END;

    PROCEDURE PrintStats;
    BEGIN
         LogExtraMessage ('JAM    Old: '+AddUpWithPreSpaces (8,Longint2String (OldSize))+
                               'b New: '+AddUpWithPreSpaces (8,Longint2String (NewSize))+
                             'b Saves: '+AddUpWithPreSpaces (3,Byte2String (UtilPercSaved (OldSize,NewSize)))+
                                 '% in '+AreaData.AreaName_F);
    END;

{Jam_PackArea}

VAR NewHeader,
    NewIndex,
    NewBody     : FILE;
    IORes       : BYTE;
    OldOffset,
    NewActive,
    NewBaseMsg,
    DayToDelete,
    NumToDelete : LONGINT;
    NumBU,Lp    : 1..MaxPacks;
    Found       : BOOLEAN;
    NextEntry   : LONGINT;
    TmpIdxRec   : JAMIDXREC;

LABEL AbortE,  { erase temp files + below }
      AbortU,  { unlock base + below }
      AbortC;  { close base }

BEGIN
     { Vul het status window }
     IF (NOT (StayQuiet OR NoFullScreen)) THEN
        WriteXY (SXb2,SYb+2,'JAM       ');

     { Bereken de datum waarop een bericht als te oud kan worden      }
     { beschouwd. Als er geen limiet bestaat, word MaxLongInt genomen }
     { dat zou de datum tot ongeveer 100 jaar in de toekomst moeten   }
     { verschuiven.                                                   }
     IF (AreaData.FidoMsgAge > 0) THEN
        DayToDelete:=GetCurrentUnixTime-(Longint (AreaData.FidoMsgAge)*SEC_Dag)
     ELSE
         DayToDelete:=0;

     { RWI 960928: er zijn nu twee doorgangen door de purge code. De }
     {             eerste verwijdert deleted berichten en op datum   }
     {             en als er dan nog te veel berichten zijn, dan     }
     {             verwijdert de tweede nog meer. De eerste doorgang }
     {             legt daarvoor een index op datum aan.             }

     NumToDelete:=0; { tijdens tweede doorgang pas }
     OldSize:=0;     { voor de statistieken }

     FOR Lp:=1 TO MaxPacks DO
     BEGIN
          PackDates[Lp]:=MAXLONGINT;
          PackFPos[Lp]:=-1; { unused }
     END;

     TmpIdxRec.UserCRC:=$FFFFFFFF;
     TmpIdxRec.HdrOffset:=$FFFFFFFF;

     REPEAT
           IF (NOT (StayQuiet OR NoFullScreen)) THEN
              IF (NumToDelete > 0) THEN
                 WriteXY (SXb2,SYb+6,'Extended purge    ')
              ELSE
                  WriteXY (SXb2,SYb+6,'Purge             ');

           NumBU:=NumToDelete;

           { zorg ervoor dat de base open is }
           IF NOT OpenBase (TRUE,AreaData.AreaName_F,AreaData.FidoMsgPath) THEN
           BEGIN
                LogMessage (liFatal,'[JAM] Unable to access '+AreaData.AreaName_F);
                Exit;
           END;

           IF (OldSize = 0) THEN
           BEGIN
                OldSize:=FileSize (IdxFile)+FileSize (TxtFile)+FileSize (HdrFile);
                NewSize:=OldSize; { in case of immediate abort }
           END;

           { En als er helemaal geen berichten zijn, waarom zouden we ons }
           { dan drukmaken ?                                              }
           IF (FileSize (IdxFile) = 0) THEN
           BEGIN
                PrintStats;
                GOTO AbortC;
           END;

           {$I-} Seek (IdxFile,0); {$I+} IORes:=IOResult;
           IF (IORes <> 0) THEN
           BEGIN
                LogDiskIOError (IORes,'[JAMPurge] Error accessing index file');
                GOTO AbortC;
           END;

           { lees de huidige header informatie }
           ReadJamHeader;
           ImportMsgs:=FALSE; { Zorg dat we alleen header lezen }

           { begin met de base te locken }
           IF (NOT LockBase) THEN
           BEGIN
                LogMessage (liFatal,'[JAM] Time-out on file lock request for '+AreaPath);
                GOTO AbortC;
           END;

           { Open een nieuwe header, en een nieuwe body file onder }
           { de eigenlijke base namen.                             }
           Assign (NewHeader,AreaPath+'.$$H');
           {$I-} ReWrite (NewHeader,1); {$I+} IORes:=IOResult;
           IF (IORes = 0) THEN
           BEGIN
                {$IFDEF LogFileIO}PostOpenF (NewHeader);{$ENDIF}

                Assign (NewBody,AreaPath+'.$$T');
                {$I-} ReWrite (NewBody,1); {$I+} IORes:=IOResult;
                IF (IORes = 0) THEN
                BEGIN
                     {$IFDEF LogFileIO}PostOpenF (NewBody);{$ENDIF}

                     Assign (NewIndex,AreaPath+'.$$I');
                     {$I-} ReWrite (NewIndex,1); {$I+} IORes:=IOResult;
                     IF (IORes = 0) THEN
                     BEGIN
                          {$IFDEF LogFileIO}PostOpenF (NewIndex);{$ENDIF}
                     END;
                END;
           END;

           IF (IORes <> 0) THEN
           BEGIN
                LogDiskIOError (IORes,'[JAM] Unable to create new base for '+AreaName);
                GOTO AbortE;
           END;

           { aktief aantal berichten }
           NewActive:=0;
           NewBaseMsg:=-1;

           { schrijf een lege header aan het begin van de nieuwe header }
           {$I-} BlockWrite (NewHeader,Jam_JHR_Header,SizeOf (Jam_JHR_Header)); {$I+}
           IORes:=IOResult;
           IF (IORes <> 0) THEN
           BEGIN
                LogDiskIOError (IORes,'[JAM] Error writing header at beginning of new file '+AreaPath);
                GOTO AbortU;
           END;

           Status.DezeTodo:=0; { geen % }
           Status.DezeArea:=0;

           WHILE ReadIndexEntry DO
           BEGIN
                Inc (Status.DezeArea);

                IF ((Status.DezeArea MOD 25) = 1) THEN
                BEGIN
                     UtilUpdateProgress;
                     Slice_Now;

                     IF KeyPressed AND (ReadKey = kEsc) THEN
                     BEGIN
                          GlobalAbort:=TRUE;
                          GOTO AbortE;
                     END;
                END;

                { Lees de bericht header en subfields in het geheugen }
                IF (NOT ReadHeaderEntry) THEN
                BEGIN
                     { Einde area }
                     LogMessage (liFatal,'[JAM] Error reading  header for msg '+Word2String (Currentmessage)+' in');
                     LogExtraMessage (areaName+', possible index failure?');
                     GOTO AbortE;
                END;

                { RWI 960928: deleted berichten zitten niet in de active }
                {             set, dus niet NumToDelete verlagen.        }
                IF ((JAM_Msg_Header.Attribute AND JMSG_DELETED) <> 0) THEN
                   Continue; { met de inner repeat }

                { RWI 960928: bij verwijdering op datum *wel* NumToDelete }
                {             verlagen.                                   }
                IF (DayToDelete <> 0) AND
                   (Jam_Msg_Header.DateProcessed <> 0) AND { RWI 961110 }
                   (Jam_MSG_Header.DateProcessed < DayToDelete)
                THEN
                    Continue; { met de inner repeat }

                { RWI 960928: als we op aantal verwijderen, dan hier checken }
                IF (NumToDelete <> 0) THEN
                BEGIN
                     Found:=FALSE;
                     FOR Lp:=1 TO NumBU DO
                         IF (PackFPos[Lp] = JamMsgHeaderOffset) THEN
                         BEGIN
                              PackFPos[Lp]:=-1;
                              PackDates[Lp]:=MAXLONGINT;
                              Dec (NumToDelete);
                              Found:=TRUE;
                         END;

                     IF Found THEN
                        Continue;
                END;

                { Als we hier aankomen mag het bericht dus blijven }
                OldOffSet:=Jam_MSG_Header.TxtOffset;
                Jam_MSG_Header.TxtOffset:=FileSize (NewBody);

                Inc (NewActive);
                IF (NewBaseMsg = -1) THEN
                   NewBaseMsg:=Jam_MSG_Header.MsgNum;

                NextEntry:=(Jam_Msg_Header.MsgNum-NewBaseMsg)*SizeOf (JAMIDXREC);
                IF (NextEntry < FilePos (NewIndex)) THEN
                BEGIN
                     LogMessage (liFatal,'[JAM] Error in message order; cannot pack or index;');
                     LogExtraMessage ('Please renumber '+AreaName);
                     GOTO AbortE;
                END;

                IF (NextEntry-FileSize (NewIndex) > 8192) THEN
                   LogMessage (liGeneral,'Advise: renumber '+AreaName+' (JAM) to save space');

                WHILE (NextEntry > FileSize (NewIndex)) DO
                BEGIN
                     {$I-} BlockWrite (NewIndex,TmpIdxRec,SizeOf (JAMIDXREC)); {$I+} IORes:=IOResult;
                     IF (IORes <> 0) THEN
                     BEGIN
                          LogDiskIOError (IORes,'Error padding new index file');
                          GOTO AbortE;
                     END;
                END;

                { schrijf een nieuw index record }
                IdxRec.HdrOffset:=FilePos (NewHeader);
                {$I-} BlockWrite (NewIndex,IdxRec,SizeOf (JAMIDXREC)); {$I+} IORes:=IOResult;
                IF (IORes <> 0) THEN
                BEGIN
                     LogDiskIOError (IORes,'[JAM] Error writing new index entry for '+AreaName);
                     GOTO AbortE;
                END;

                AddPack (FilePos (NewHeader),Jam_MSG_Header.DateProcessed);

                { schrijft de nieuwe header naar disk }
                {$I-} BlockWrite (NewHeader,Jam_MSG_Header,SizeOf (Jam_MSG_Header)); {$I+}
                IORes:=IOResult;
                IF (IORes <> 0) THEN
                BEGIN
                     LogDiskIOError (IORes,'[JAM] Error writing to new areabase files in '+AreaName);
                     GOTO AbortE;
                END;

                { Kopieer de header en body naar de nieuwe base }
                IF (NOT CopyFromFile (HdrFile,NewHeader,CurrentHeader+SizeOf (Jam_MSG_Header),Jam_MSG_Header.SubFieldLen)) OR
                   (NOT CopyFromFile (TxtFile,NewBody,OldOffset,Jam_MSG_Header.TxtLen)) THEN
                BEGIN
                     LogMessage (liFatal,'[JAM] Error copying to new base for '+AreaName);
                     GOTO AbortE;
                END;

           END; { while }

           { RWI 961021: voorkom dat BaseMsgNum -1 wordt!! }
           IF (NewBaseMsg = -1) THEN
              NewBaseMsg:=1;

           { schrijf opnieuw een header naar disk, maar nu met nieuwe gegevens }
           WITH Jam_JHR_header DO
           BEGIN
                Inc (ModCounter);
                ActiveMsgs:=NewActive;
                BaseMsgNum:=NewBaseMsg;
           END; { with }

           { Schrijf de nieuwe JAM header naar disk. Letop! Dit moet naar }
           { de NIEUWE database, dus niet WriteJamHeader gebruiken! }
           {$I-}
           Seek (NewHeader,0);
           BlockWrite (NewHeader,Jam_JHR_Header,SizeOf (Jam_JHR_Header));
           {$I+}
           IORes:=IOResult;
           IF (IORes <> 0) THEN
           BEGIN
                LogDiskIOError (IORes,'[JAM] Error rewriting header at beginning of new file '+AreaPath);
                GOTO AbortE;
           END;

           { sluit de Base files }
           UnlockBase;
           CloseBase;
           AreaPath:=AreaData.FidoMsgPath;

           { statistieken }
           NewSize:=FileSize (NewIndex)+FileSize (NewBody)+FileSize (NewHeader);

           { Probeer de originele files te verwijderen }
           {$I-}
           {$IFDEF LogFileIO}PreCloseF (NewHeader);{$ENDIF}
           Close (NewHeader);

           {$IFDEF LogFileIO}PreCloseF (NewBody);{$ENDIF}
           Close (NewBody);

           {$IFDEF LogFileIO}PreCloseF (NewIndex);{$ENDIF}
           Close (NewIndex);
           PeekFiles;

           Erase (HdrFile);
           Erase (TxtFile);
           Erase (IdxFile);

           Rename (NewHeader,AreaPath+EXT_HDRFILE);
           Rename (NewBody,AreaPath+EXT_TXTFILE);
           Rename (NewIndex,AreaPath+EXT_IDXFILE);
           {$I+}
           IORes:=IOResult;
           IF (IOResult <> 0) THEN
           BEGIN
                LogDiskIOError (IORes,'[JAM] Error while putting new base in place for '+AreaName);
                Exit;
           END;

           { RIW 960928: Als er meer Active messages zijn, dan er maximaal }
           {             toegestaan zijn, bereken dan hoeveel berichten    }
           {             het loodje moeten leggen.                         }

           IF (AreaData.FidoMsgLimit > 0) THEN
           BEGIN
                NumToDelete:=Jam_JHR_header.ActiveMsgs-AreaData.FidoMsgLimit;

                { RWI 960928: controle toegevoegd! }
                IF (NumToDelete < 0) THEN
                   NumToDelete:=0;

                { RWI 960928: meer dan we bijgehouden hebben kunnen we in }
                {             e'e'n run niet verwijderen, dus limiteer.   }
                IF (NumToDelete > MaxPacks) THEN
                   NumToDelete:=MaxPacks;
           END ELSE
               NumToDelete:=0;

     UNTIL (NumToDelete = 0);

     { beetje statistieken }
     Status.SavedBytes:=Status.SavedBytes+(OldSize-NewSize);

     PrintStats;

     Exit;

AbortE:

     {$I-}
     {$IFDEF LogFileIO}PreCloseF (NewHeader);{$ENDIF}
     Close (NewHeader); IORes:=IOResult;

     IF (IORes = 0) THEN
     BEGIN
          Erase (NewHeader);
          IORes:=IOResult;
     END;

     {$IFDEF LogFileIO}PreCloseF (NewBody);{$ENDIF}
     Close (NewBody); IORes:=IOResult;

     IF (IORes = 0) THEN
     BEGIN
          Erase (NewBody);
          IORes:=IOResult;
     END;

AbortU:
     UnlockBase; { RWI 960928 }

AbortC:
     CloseBase;  { RWI 960928 }
END;


(*
{ ------------------------------------------------------------------------ }
{ JAM LINK ROUTINE                                                         }
{                                                                          }
{ Jam gebruikt een uitgebreide link routine, dat het mogenlijk maakt om    }
{ meerdere reply's op een bericht te geven.                                }
{ Omdat een Jam base in principe ongelimiteerd is, moeten we gebruik       }
{ maken van een tijdelijke file op disk, als er meer dan 2700 berichten    }
{ in een gebied staan, of als we weinig geheugen meer hebben.              }

TYPE ReplyRecord = RECORD
                         xOffSet,
                         xMsgID,
                         xReplyID,
                         xReplyTo,
                         xReply1,
                         xReply2   : LONGINT;
                   END;

{--------------------------------------------------------------------------}
{ DiskLinkArea                                                             }
{                                                                          }
FUNCTION DiskLinkArea (AreaData : AreaBaseRecord) : BOOLEAN;

VAR TmpFile   : FILE;
    ReadEntry,
    FoundItem : ReplyRecord;
    Res,
    Pos       : LONGINT;
    IORes     : BYTE;

    { Zoek door de lijst naar een MsgId dat klopt, tegelijkertijd ook }
    { op zoek naar een ReplyId ... scheelt een loop door de file.     }

    FUNCTION FindItem (MsgId : LONGINT; VAR Found : ReplyRecord; VAR Pos : LONGINT) : WORD;

    VAR Temp : ReplyRecord;
        Flag : LONGINT;

    BEGIN
         Flag:=-1;
         FindItem := 0 {False};
         Seek (TmpFile,0);

         WHILE (NOT Eof (TmpFile)) DO
         BEGIN
              Pos:=FilePos (TmpFile);
              BlockRead (TmpFile,Temp,SizeOf (ReplyRecord));

              IF (Temp.xMsgId = MsgId) THEN
              BEGIN
                   Found:=Temp;
                   FindItem:=1{=Found};
                   Exit;
              END;

              IF (Temp.xReplyID = MsgId) AND (Flag = -1) THEN
              BEGIN
                   FindItem:=2; { Found secondary }
                   Flag:=Pos;
                   Found:=Temp;
              END;
         END; { while }

         IF (Flag <> -1) THEN
            Pos:=Flag;
    END;

    FUNCTION ReadItem (Id : LONGINT; VAR Invoer : ReplyRecord; VAR Pos : LONGINT) : BOOLEAN;
    BEGIN
         IF (Id <> 0) THEN
         BEGIN
              ReadItem:=TRUE;

              {$I-} Seek (TmpFile,0); {$I+}
              WHILE (NOT Eof (TmpFile)) DO
              BEGIN
                   Pos:=FilePos (TmpFile);
                   BlockRead (TmpFile,Invoer,SizeOf (Invoer));
                   IF (Invoer.xOffset = Id) THEN
                   BEGIN
                        Seek (TmpFile,Pos);
                        Exit;
                   END;
              END; { while }
         END;

         ReadItem:=FALSE;
    END;

    PROCEDURE SaveItem (P : LONGINT; Uitvoer : ReplyRecord);
    BEGIN
         IF (P <> FilePos (TmpFile)) THEN
            Seek (TmpFile,P);
         Blockwrite (TmpFile,Uitvoer,SizeOf (Uitvoer));
    END;

{ DiskLinkArea }

LABEL Verder,AbortHere;

VAR Aborted : BOOLEAN;

BEGIN
     DiskLinkArea:=FALSE; { not aborted }

     { Vul het status window }
     IF (NOT (StayQuiet OR NoFullScreen)) THEN BEGIN
        WriteXY (SXb2,SYb+2,'JAM (Disk)');
        WriteXY (SXb2,SYb+6,'Scanning');
     END;

     { Doorloop de area bericht voor bericht }
     IF (NOT GetFirstMessage) THEN
     BEGIN
          CloseBase; { RWI 960828 }
          Exit;
     END;

     Assign (TmpFile,'JAMLINK.$$$');
     {$I-} ReWrite (TmpFile,1); {$I+} IORes:=IOResult;
     IF (IORes <> 0) THEN
     BEGIN
          LogDiskIOError (IORes,'[JamDiskLink] Unable to open tempory file');
          Exit;
     END;

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

     Status.DezeArea:=0;
     Status.DezeToDo:=0;
     Aborted:=FALSE;

     REPEAT
           IF (NOT ReadHeaderEntry) THEN
           BEGIN
                LogMessage ('[JamDiskLink] Error reading msg header for '+AreaName+
                            ' in msg '+Word2String (Currentmessage)+', possible index failure ?');
                { Einde area }
                CloseBase;
                Exit;
           END;

           { Vul een entry in om naar disk te schrijven }
           WITH ReadEntry DO
           BEGIN
                xOffSet:=Jam_Msg_Header.MsgNum;
                xMsgId:=JAM_Msg_Header.MsgIdCRC;
                xReplyID:=JAM_Msg_Header.ReplyCRC;
                xReplyto:=0;
                xReply1:=0;
                xReply2:=0;
           END;

           { Sla dit hele spulletje over als de de reply link toch niet bestaat }
           IF (Jam_Msg_Header.ReplyCRC = $FFFFFFFF) THEN
              GOTO Verder;

           { Zoek naar een entry op basis van de reply id }
           Res:=FindItem (ReadEntry.xReplyID,FoundItem,Pos);

           IF (Res = 0) THEN
              GOTO Verder;

           IF (Res = 1) THEN
           BEGIN
                { Kijk of het eerste reply link leeg is. Dit is zo als dit }
                { de eerst reply naar een bericht is.                      }
                IF (FoundItem.xReply1 = 0) THEN
                BEGIN
                     FoundItem.xReply1:=JAM_Msg_Header.MsgNum;
                     ReadEntry.xReplyTo:=FoundItem.xOffset;
                     SaveItem (Pos,FoundItem);
                     GOTO Verder;
                END;

                IF (FoundItem.xReply2 = 0) THEN
                BEGIN
                     FoundItem.xReply2:=JAM_Msg_Header.MsgNum;
                     ReadEntry.xReplyTo:=FoundItem.xOffset;
                     SaveItem (Pos,FoundItem);
                     GOTO Verder;
                END;
           END;

           IF (Res = 2) THEN
           BEGIN
                { Kijk of het eerste reply link leeg is }
                { Dit is zo als dit de eerst reply naar }
                { een bericht is.                       }
                IF (FoundItem.xReply1 = 0) THEN
                BEGIN
                     FoundItem.xReply1:=JAM_Msg_Header.MsgNum;
                     ReadEntry.xReplyTo:=FoundItem.xOffset;
                     SaveItem (Pos,FoundItem);
                     GOTO Verder;
                END;

                IF (FoundItem.xReply2 = 0) THEN
                BEGIN
                     FoundItem.xReply2:=JAM_Msg_Header.MsgNum;
                     ReadEntry.xReplyTo:=FoundItem.xOffset;
                     SaveItem (Pos,FoundItem );
                     GOTO Verder;
                END;
           END;

           REPEAT
                 { Kijk of de 2e link vrij is }
                 IF (FoundItem.xReply2 = 0) AND
                    (FoundItem.xReplyID = ReadEntry.xReplyID) AND
                    (FoundItem.xReplyID <> $FFFFFFFF) THEN
                 BEGIN
                      FoundItem.xReply2:=JAM_Msg_Header.MsgNum;
                      ReadEntry.xReplyTo:=FoundItem.xReplyto;
                      SaveItem (Pos,FoundItem);
                      Break; { uit de repeat }
                 END;

           UNTIL (NOT ReadItem (FoundItem.xReply2,FoundItem,Pos));

           Slice_Now;

           IF KeyPressed AND (ReadKey = kEsc) THEN
           BEGIN
                Aborted:=TRUE;
                Break;
           END;

Verder:
           SaveItem (FileSize (TmpFile),ReadEntry);
           Inc (Status.DezeToDo);
           UtilUpdateProgress;

     UNTIL (NOT GetNextMessage);

     IF Aborted THEN
        GOTO AbortHere;

     { ok, disk implementatie klaar .. nu voor de header update }
     IF (NOT (StayQuiet OR NoFullScreen)) THEN
        WriteXY (SXb2,SYb+6,'Linking ');

     Seek (TmpFile,0);
     WHILE (NOT Eof (TmpFile)) DO
     BEGIN
          Inc (Status.DezeArea);

          {$I-} BlockRead (TmpFile,FoundItem,Sizeof (FoundItem)); {$I+}
          IORes:=IOResult; { RWI 960828: nodig! }

          { Het heeft alleen zin om een record te updaten als er werkelijk }
          { links gelegt kunnen worden.                                    }

          IF (FoundItem.xReplyTo = 0) AND
             (FoundItem.xReply1 = 0) AND
             (FoundItem.xReply2 = 0)
          THEN
              Continue;

          IF ((Status.DezeArea MOD 24) = 1) THEN
          BEGIN
               UtilUpdateProgress;

               Slice_Now;

               IF KeyPressed AND (ReadKey = kEsc) THEN
               BEGIN
                    Aborted:=TRUE;
                    Break;
               END;
          END;

          { ok, we komen er niet onderuit. Probeer links te leggen }
          IF FindByIndex (FoundItem.xOffset) THEN
          BEGIN
               { Lees de header in, verander deze .. en schrijf 'm weer weg }
               IF (NOT ReadHeaderEntry) THEN
               BEGIN
                    LogMessage ('[JamDiskLink] Error reading msg header for '+AreaName+' in msg '+
                                Word2string (Currentmessage)+', possible index failure ?');
                    { Einde area }
                    Exit;
               END;

               WITH Jam_Msg_Header DO
               BEGIN
                    ReplyTo:=FoundItem.xReplyTo;
                    Reply1st:=FoundItem.xReply1;
                    ReplyNext:=FoundItem.xReply2;
               END;

               {$I-}
               Seek (HdrFile,CurrentHeader);
               BlockWrite (HdrFile,Jam_MSG_Header,SizeOf (Jam_MSG_Header ));
               {$I+}
               IORes:=IOResult;

               IF (IORes <> 0) THEN
               BEGIN
                    LogDiskIOError (IORes,'[JamDiskLink] Error writing to header');
                    Exit;
               END;
          END; { if }
     END; { while }

AbortHere:

     {$I-}
     {$IFDEF LogFileIO}PreCloseF (TmpFile);{$ENDIF}
     Close (TmpFile);
     Erase (TmpFile);
     {$I+}
     IORes:=IOResult;

     DiskLinkArea:=Aborted;
END;


{--------------------------------------------------------------------------}
{ JAM-Link in memory                                                       }
{                                                                          }
{ Als er minder dan 2700 berichten in een gebied staan, link dan gewoon in }
{ het geheugen.                                                            }
{                                                                          }
FUNCTION MemLinkArea (AreaData : AreaBaseRecord) : BOOLEAN;

TYPE ReplyMem = ARRAY[1..1] OF ReplyRecord; { 24 bytes per record }

VAR TmpFile     : ^ReplyMem;
    ItemTeller,
    AantalItems : LONGINT;
    ReadEntry,
    FoundItem   : ReplyRecord;
    Res,
    Pos         : LONGINT;
    IORes       : BYTE;

    { Zoek door de lijst naar een MsgId dat klopt, tegelijkertijd ook }
    { op zoek naar een ReplyId ... scheelt een loop door de file.     }

    FUNCTION FindItem (MsgId : LONGINT; VAR Found : ReplyRecord; VAR Pos : LONGINT) : WORD;

    VAR Temp   : ReplyRecord;
        Flag,
        Teller : LONGINT;
        IORes  : BYTE;

    BEGIN
         Flag:=-1;
         FindItem:=0{=False};

         FOR Teller:=1 TO AantalItems DO
         BEGIN
              Pos:=Teller;
              Temp:=TmpFile^[Teller];

              IF (Temp.xMsgId = MsgId) THEN
              BEGIN
                   Found:=TmpFile^[Teller];
                   FIndItem:=1; {Found}
                   Exit;
              END;

              IF (Flag = -1) AND (Temp.xReplyID = MsgId) THEN
              BEGIN
                   FindItem:=2; { Found secondary }
                   Flag:=Pos;
                   Found:=TmpFile^[Teller];
              END;
         END; { for }

         IF (Flag <> -1) THEN
            Pos:=Flag;
    END;

    FUNCTION ReadItem (Id : LONGINT; VAR Invoer : ReplyRecord; VAR Pos : LONGINT) : BOOLEAN;

    VAR Teller : LONGINT;

    BEGIN
         IF (Id <> 0) THEN
         BEGIN
              ReadItem:=TRUE;

              FOR Teller:=1 TO AantalItems DO
              BEGIN
                   Pos:=Teller;
                   Invoer:=TmpFile^[Teller];
                   IF (Invoer.xOffset = Id) THEN
                      Exit;
              END; { for }
         END;

         ReadItem:=FALSE;
    END;


{MemLinkArea}

LABEL Verder,AbortHere;

VAR Aborted : BOOLEAN;

BEGIN
     MemLinkArea:=FALSE; { not aborted }

     { Vul het status window }
     IF (NOT (StayQuiet OR NoFullScreen)) THEN BEGIN
        WriteXY (SXb2,SYb+2,'JAM (Mem) ');
        WriteXY (SXb2,SYb+6,'Scanning');
     END;

     { doorloop de area bericht voor bericht }
     IF (NOT GetFirstMessage) THEN
     BEGIN
          CloseBase; { RWI 970127 }
          Exit;
     END;

     { vraag het geheugen aan voor de routine }
     GetMem (TmpFile,65300);
     AantalItems:=0;

     Status.DezeArea:=0;
     Status.DezeToDo:=0;
     Aborted:=FALSE;

     REPEAT
           IF (NOT ReadHeaderEntry) THEN
           BEGIN
                LogMessage ('[JamMemLink] Error reading message header for '+AreaName+' in msg '+
                            Word2String (Currentmessage)+', possible index failure ?');
                { einde area }
                CloseBase; { RIW 970127 }
                Exit;
           END;

           { Vul een entry in om naar disk te schrijven }
           WITH ReadEntry DO
           BEGIN
                xOffSet:=Jam_Msg_Header.MsgNum;
                xMsgId:=JAM_Msg_Header.MsgIdCRC;
                xReplyID:=JAM_Msg_Header.ReplyCRC;
                xReplyto:=0;
                xReply1:=0;
                xReply2:=0;
           END; { with }

           { sla dit hele spulletje over als de de reply link toch niet }
           { bestaat.                                                   }
           IF (Jam_Msg_Header.ReplyCRC = $FFFFFFFF) THEN
              GOTO Verder;

           { zoek naar een entry op basis van de reply id }
           Res:=FindItem (ReadEntry.xReplyID,FoundItem,Pos);

           IF (Res = 0) THEN
              GOTO Verder;

           IF (Res = 1) THEN
           BEGIN
                { Kijk of het eerste reply link leeg is Dit is zo als }
                { dit de eerst reply naar een bericht is.             }
                IF (FoundItem.xReply1 = 0) THEN
                BEGIN
                     FoundItem.xReply1:=JAM_Msg_Header.MsgNum;
                     ReadEntry.xReplyTo:=FoundItem.xOffset;
                     TmpFile^[Pos]:=FoundItem;
                     GOTO Verder;
                END;

                IF (FoundItem.xReply2 = 0) THEN
                BEGIN
                     FoundItem.xReply2:=JAM_Msg_Header.MsgNum;
                     ReadEntry.xReplyTo:=FoundItem.xOffset;
                     TmpFile^[Pos]:=FoundItem;
                     GOTO Verder;
                END;
           END; { res = 1 }

           IF (Res = 2) THEN
           BEGIN
                { kijk of het eerste reply link leeg is. Dit is zo als }
                { dit de eerst reply naar een bericht is.              }
                IF (FoundItem.xReply1 = 0) THEN
                BEGIN
                     FoundItem.xReply1:=JAM_Msg_Header.MsgNum;
                     ReadEntry.xReplyTo:=FoundItem.xOffset;
                     TmpFile^[Pos]:=FoundItem;
                     GOTO Verder;
                END;

                IF (FoundItem.xReply2 = 0) THEN
                BEGIN
                     FoundItem.xReply2:=JAM_Msg_Header.MsgNum;
                     ReadEntry.xReplyTo:=FoundItem.xOffset;
                     TmpFile^[Pos]:=FoundItem;
                     GOTO Verder;
                END;
           END; { res = 2 }

           REPEAT
                 { Kijk of de 2e link vrij is }
                 IF (FoundItem.xReply2 = 0) AND
                    (FoundItem.xReplyID = ReadEntry.xReplyID) AND
                    (FoundItem.xReplyID <> $FFFFFFFF) THEN
                 BEGIN
                      FoundItem.xReply2:=JAM_Msg_Header.MsgNum;
                      ReadEntry.xReplyTo:=FoundItem.xReplyto;
                      TmpFile^[Pos]:=FoundItem;
                      Break; { uit de while }
                 END;

           UNTIL (NOT ReadItem (FoundItem.xReply2,FoundItem,Pos));

           Slice_Now;

           IF KeyPressed AND (ReadKey = kEsc) THEN
           BEGIN
                Aborted:=TRUE;
                Break; { from the repeat/until }
           END;

Verder:
           Inc (AantalItems);
           TmpFile^[AantalItems]:=ReadEntry;

           Inc (Status.DezeToDo);
           UtilUpdateProgress;

     UNTIL (NOT GetNextMessage);

     IF Aborted THEN
        GOTO AbortHere;

     { ok, memory implementatie klaar .. nu voor de header update }
     IF (NOT (StayQuiet OR NoFullScreen)) THEN
        WriteXY (SXb2,SYb+6,'Linking ')
     ELSE
        Writeln ('Linking JAM areas...');

     FOR ItemTeller:=1 TO AantalItems-1 DO
     BEGIN
          FoundItem:=TmpFile^[ItemTeller];

          Inc (Status.DezeArea);

          { Het heeft alleen zin om een record te updaten als er werkelijk }
          { links gelegt kunnen worden.                                    }

          IF (FoundItem.xReplyTo = 0) AND
             (FoundItem.xReply1 = 0) AND
             (FoundItem.xReply2 = 0)
          THEN
              Continue;

          IF ((Status.DezeArea MOD 24) = 1) THEN
          BEGIN
               UtilUpdateProgress;

               Slice_Now;

               IF KeyPressed AND (ReadKey = kEsc) THEN
               BEGIN
                    Aborted:=TRUE;
                    Break;
               END;
          END;

          { ok, we komen er niet onderuit. Probeer links te leggen }
          IF FindByIndex (FoundItem.xOffset) THEN
          BEGIN
               { Lees de header in, verander deze .. en schrijf 'm weer weg }
               IF (NOT ReadHeaderEntry) THEN
               BEGIN
                    LogMessage ('[JamMemLink] Error reading msg header for '+AreaName+' in msg '+
                                Word2String (Currentmessage)+', possible index failure?');
                    { Einde area }
                    CloseBase;
                    Exit;
               END;

               WITH Jam_Msg_Header DO
               BEGIN
                    ReplyTo:=FoundItem.xReplyTo;
                    Reply1st:=FoundItem.xReply1;
                    Replynext:=FoundItem.xReply2;
               END;

               {$I-}
               Seek (HdrFile,CurrentHeader);
               BlockWrite (HdrFile,Jam_MSG_Header,SizeOf (Jam_MSG_Header));
               {$I+}
               IORes:=IOResult;

               IF (IORes <> 0) THEN
               BEGIN
                    LogDiskIOError (IORes,'[JamMemLink] Error writing to header');
                    Exit;
               END;
          END; { if }
     END; { for }

AbortHere:

     FreeMem (TmpFile,65300);
     IORes:=IOResult;

     MemLinkArea:=Aborted;
END;
*)

{--------------------------------------------------------------------------}
{ Jam_LinkArea                                                             }
{                                                                          }
{ This routine links all the messages in the given JAM base. TRUE is       }
{ returned when the link is aborted by the user; FALSE on normal           }
{ completion.                                                              }
{                                                                          }
{ How to link the messages:                                                }
{  - There are three fields in each JAMHDR structure: ReplyTo, Reply1st    }
{    and ReplyNext. The first is the link back to the previous message.    }
{    Reply1st is the link to the message that is the first reply to this   }
{    message. If there are more replies then ReplyNext points to the next  }
{    message that is also a reply to the same original message this        }
{    message is a reply to.                                                }
{  - The same JAMHDR structure convienently contains the MsgIdCRC and      }
{    ReplyCRC fields, which saves us from finding these two headers. Let's }
{    hope all programs adding messages use the same method to calculate    }
{    this value, as stated in the docs:                                    }
{   (3) When calculating the CRC-32 of the MSGID and REPLY lines, the      }
{       text ^aMSGID: and ^aREPLY: should be removed as well as all        }
{       leading and trailing white space characters.                       }
{ This is how the link is performed:                                       }
{  1 - Follow the index to find all not-deleted message and read all       }
{      MsgIdCRC and ReplyCRC fields from the JAMHDR structure. Save the    }
{      position of the JAMHDR structure for updating later on.             }
{  2 - Perform the link.                                                   }
{  3 - Update all JAMHDR structures on disk by filling in the new fields.  }
{      Might be able to do a dirty blockwrite in the middle of the JAMHDR  }
{      to avoid loading it from disk.                                      }
{                                                                          }
FUNCTION Jam_LinkArea (AreaRec : AreaBaseRecord) : BOOLEAN;

TYPE MsgInfoRecordPtr = ^MsgInfoRecord;

     MsgInfoRecord = RECORD
                           HdrPos    : LONGINT;
                           MsgNum    : LONGINT;
                           ReplyCRC  : LONGINT;
                           ReplyTo   : LONGINT;
                           Reply1st  : MsgInfoRecordPtr;
                           ReplyNext : MsgInfoRecordPtr;
                     END;

CONST MAX_MSGS = 65535 DIV SizeOf (MsgInfoRecordPtr);

TYPE MsgInfoArray = ARRAY[1..MAX_MSGS] OF MsgInfoRecordPtr;
     MsgIdArray   = ARRAY[1..MAX_MSGS] OF LONGINT; { CRCs }

VAR MsgInfo  : ^MsgInfoArray;
    MsgIds   : ^MsgIdArray;
    MsgCount : 0..MAX_MSGS;

    {----------------------------------------------------------------------}
    { AddToReplies                                                         }
    {                                                                      }
    { This routine fills in the forward reply thread. If the original      }
    { message indicated by InfoNr does not have a reply yet, then the      }
    { pointed to message is the first reply. Otherwise, the thread is      }
    { followed until a reply record does not have ReplyNext filled in yet, }
    { in which case the pointer is filled-in in that records' ReplyNext    }
    { field. During writing to disk all MsgNum's are filled in instead,    }
    { but pointers are easier to follow the threads in memory here.        }
    {                                                                      }
    PROCEDURE AddToReplies (InfoNr : WORD; ReplyPtr : MsgInfoRecordPtr);

    VAR CurrPtr : MsgInfoRecordPtr;

    BEGIN
         { the first reply is pointed to by Reply1st }
         IF (MsgInfo^[InfoNr]^.Reply1st = NIL) THEN
         BEGIN
              MsgInfo^[InfoNr]^.Reply1st:=ReplyPtr;
              Exit;                                { ## EXIT ## }
         END;

         { ReplyNext of this record points to the next msg in the  }
         { reply thread this message is part of.                   }
         { Since we know the original message, we follow the       }
         { Reply1st and then ReplyNext pointers until the end and  }
         { then add this message.                                  }

         CurrPtr:=MsgInfo^[InfoNr]^.Reply1st;
         WHILE (CurrPtr^.ReplyNext <> NIL) DO
               CurrPtr:=CurrPtr^.ReplyNext;

         CurrPtr^.ReplyNext:=ReplyPtr;
    END;

    {----------------------------------------------------------------------}
    { FindMsgId                                                            }
    {                                                                      }
    { This routine searches the MsgIds array for the given value. If found }
    { it returns the offset into the array, otherwise 0.                   }
    {                                                                      }
    FUNCTION FindMsgId (Value : LONGINT; OwnNr : WORD) : WORD;

    VAR Lp : 0..MAX_MSGS;

    BEGIN
         {## speed up with assembly }
         FOR Lp:=1 TO MsgCount DO
             IF (MsgIds^[Lp] = Value) AND (Lp <> OwnNr) THEN
             BEGIN
                  FindMsgId:=Lp;
                  Exit;
             END;

         FindMsgId:=0; { not found }
    END;

{Jam_LinkArea}

VAR Lp       : 0..MAX_MSGS;
    Aborted  : BOOLEAN;
    IORes    : BYTE;
    InfoNr   : 0..MAX_MSGS;

LABEL Abort,Normal;

BEGIN
     JAM_LinkArea:=FALSE; { not aborted }

     { Vul het status window }
     IF (NOT (StayQuiet OR NoFullScreen)) THEN
        WriteXY (SXb2,SYb+2,'Jam        ');

     MsgInfo:=NIL;
     Aborted:=FALSE;

     { try to open the base }
     IF (NOT OpenBase ({DenyAll:}TRUE,AreaRec.AreaName_F,AreaRec.FidoMsgPath)) THEN
     BEGIN
          LogMessage (liFatal,'[JamLink] Failed to initialize');
          GOTO Abort;
     END;

     IF (Jam_JHR_header.ActiveMsgs = 0) THEN
        GOTO Abort; { nothing to link }

     IF (Jam_JHR_header.ActiveMsgs > MAX_MSGS) THEN
     BEGIN
          LogMessage (liFatal,'[JamLink] Too many message for link operation');
          GOTO Abort;
     END;

     IF (NOT LockBase) THEN
        GOTO Abort;

     { work out how many messages there are and allocate the array of }
     { pointers and initialise it with NIL.                           }
     IF (_MaxAvail < Jam_JHR_header.ActiveMsgs*SizeOf (MsgInfoRecordPtr)) THEN
     BEGIN
          LogMessage (liFatal,'[JamLink] Not enough memory (1)');
          GOTO Abort;
     END;

     { allocate memory for the array }
     GetMem (MsgInfo,Jam_JHR_header.ActiveMsgs*SizeOf (MsgInfoRecordPtr));

     IF (_MaxAvail < Jam_JHR_header.ActiveMsgs*SizeOf (LONGINT)) THEN
     BEGIN
          LogMessage (liFatal,'[JamLink] Not enough memory (2)');
          FreeMem (MsgInfo,Jam_JHR_header.ActiveMsgs*SizeOf (MsgInfoRecordPtr));
          MsgInfo:=NIL;
          GOTO Abort;
     END;

     GetMem (MsgIds,Jam_JHR_header.ActiveMsgs*SizeOf (LONGINT));
     FillChar (MsgIds^,Jam_JHR_header.ActiveMsgs*SizeOf (LONGINT),0);

     MsgCount:=0;

     { === 1 - Read from disk === }

     IF (NOT (StayQuiet OR NoFullScreen)) THEN
        WriteXY (SXb2,SYb+6,'Scanning');
     Status.DezeToDo:=Jam_JHR_header.ActiveMsgs;

     { read each SQI record, then read the frame from the .SQD file to }
     { get the rest of the info. The index has been read into memory   }
     { so we can use it right away.                                    }

     Status.DezeArea:=0;
     UtilUpdateProgress;

     FOR Lp:=1 TO Jam_JHR_header.ActiveMsgs DO
     BEGIN
          IF (NOT ReadHeaderEntry) THEN
          BEGIN
               LogMessage (liFatal,'[JamLink] Error reading header');
               GOTO Abort;
          END;

          IF (_MaxAvail < SizeOf (MsgInfoRecord)+1000) THEN
          BEGIN
               LogMessage (liFatal,'Not enough memory; not all messages might be linked');
               Break; { from the for }
          END;

          { check for an OK frame, then copy the needed info }
          IF ((Jam_MSG_header.Attribute AND JMSG_DELETED) = 0) THEN
          BEGIN
               { not-deleted and recognised message type }
               Inc (MsgCount);

               { store the message info }
               MsgIds^[MsgCount]:=Jam_MSG_header.MsgIdCRC;

               GetMem (MsgInfo^[MsgCount],SizeOf (MsgInfoRecord));

               WITH MsgInfo^[MsgCount]^ DO
               BEGIN
                    HdrPos:=JamMsgHeaderOffset;
                    MsgNum:=Jam_MSG_header.MsgNum;
                    ReplyCRC:=Jam_MSG_header.ReplyCRC;
                    ReplyTo:=0;
                    Reply1st:=NIL;
                    ReplyNext:=NIL;
               END; { with }

          END; { if non-deleted message }

          { skip all kludges so we get to the next header }
          {$I-} Seek (HdrFile,FilePos (HdrFile)+Jam_MSG_header.SubfieldLen); {$I+}
          IORes:=IOResult;
          IF (IORes <> 0) THEN
          BEGIN
               LogDiskIOError (IORes,'[JamLink] Error seeking to next msg');
               GOTO Abort;
          END;

          { time slicing and abort check }
          IF ((Lp MOD 10) = 0) THEN
          BEGIN
               Status.DezeArea:=Lp;
               UtilUpdateProgress;

               Slice_Now;

               IF KeyPressed AND (ReadKey = kEsc) THEN
               BEGIN
                    Aborted:=TRUE;
                    GOTO Abort;
               END;
          END;

     END; { for }

     { === 2 - Link === }

     IF (NOT (StayQuiet OR NoFullScreen)) THEN
        WriteXY (SXb2,SYb+6,'Linking ');
     Status.DezeToDo:=MsgCount;
     Status.DezeArea:=0;
     UtilUpdateProgress;

     FOR Lp:=1 TO MsgCount DO
         IF (MsgInfo^[Lp] <> NIL) THEN
         BEGIN
              WITH MsgInfo^[Lp]^ DO
              BEGIN
                   IF (ReplyCRC <> -1) THEN
                   BEGIN
                        InfoNr:=FindMsgId (ReplyCRC,Lp);
                        IF (InfoNr <> 0) THEN
                        BEGIN
                             { fill in the backwards reply info }
                             ReplyTo:=MsgInfo^[InfoNr]^.MsgNum;

                             { add our MsgNum to the forward list of }
                             { replies of the message this is a reply to }
                             AddToReplies (InfoNr,MsgInfo^[Lp]);
                        END;
                   END;
              END; { with }

              { time slicing and abort check }
              IF ((Lp MOD 10) = 0) THEN
              BEGIN
                   Status.DezeArea:=Lp;
                   UtilUpdateProgress;

                   Slice_Now;

                   IF KeyPressed AND (ReadKey = kEsc) THEN
                   BEGIN
                        Aborted:=TRUE;
                        GOTO Abort;
                   END;
              END;

         END; { for }

     { === 3 - Write to disk === }

     IF (NOT (StayQuiet OR NoFullScreen)) THEN
        WriteXY (SXb2,SYb+6,'Updating');
     Status.DezeArea:=0;
     UtilUpdateProgress;

     FOR Lp:=1 TO MsgCount DO
     BEGIN
          { read the original frame header, update the fields, and }
          { write it back.                                         }

          WITH MsgInfo^[Lp]^ DO
          BEGIN
               {## change into a dirty blockwrite to avoid reading the }
               {   header from disk first.                             }
               { load the complete message header }
               Seek (HdrFile,HdrPos);
               IF (NOT ReadHeaderEntry) THEN
               BEGIN
                    LogMessage (liFatal,'[JamLink] Error updating header (1)');
                    GOTO Abort;
               END;

               { update the fields }
               Jam_MSG_Header.ReplyTo:=ReplyTo;

               IF (Reply1st <> NIL) THEN
                  Jam_MSG_Header.Reply1st:=Reply1st^.MsgNum
               ELSE
                   Jam_MSG_Header.Reply1st:=0;

               IF (ReplyNext <> NIL) THEN
                  Jam_MSG_Header.ReplyNext:=ReplyNext^.MsgNum
               ELSE
                   Jam_MSG_Header.ReplyNext:=0;

               { write the updated message header to disk }
               Seek (HdrFile,HdrPos);
               {$I-} BlockWrite (HdrFile,Jam_MSG_Header,SizeOf (Jam_MSG_Header)); {$I+} IORes:=IOResult;
               IF (IORes <> 0) THEN
               BEGIN
                    LogDiskIOError (IORes,'[JamLink] Error updating header (2)');
                    GOTO Abort;
               END;

          END; { with }

          { time slicing and abort check }
          IF ((Lp MOD 10) = 0) THEN
          BEGIN
               Status.DezeArea:=Lp;
               UtilUpdateProgress;

               Slice_Now;

               IF KeyPressed AND (ReadKey = kEsc) THEN
               BEGIN
                    Aborted:=TRUE;
                    GOTO Abort;
               END;
          END;

     END; { for }

     Status.DezeArea:=MsgCount; { get to 100% }

     GOTO Normal;

Abort:
     LogMessage (liGeneral,'Aborting link of '+AreaRec.AreaName_F);

Normal:

     { free all allocated memory }
     IF (MsgInfo <> NIL) THEN
     BEGIN
          WHILE (MsgCount > 0) DO
          BEGIN
               FreeMem (MsgInfo^[MsgCount],SizeOf (MsgInfoRecord));
               Dec (MsgCount);
          END; { while }

          FreeMem (MsgInfo,Jam_JHR_header.ActiveMsgs*SizeOf (MsgInfoRecordPtr));
          FreeMem (MsgIds,Jam_JHR_header.ActiveMsgs*SizeOf (LONGINT));
     END;

     { close the base and free the memory; if allocated }
     UnlockBase;
     CloseBase;

     JAM_LinkArea:=Aborted;
END;


{---------------------------------------------------------------------------}
{ Jam_RenumberArea                                                          }
{                                                                           }
{ Deze routine hernummert de berichten in de opgegeven areas en voert       }
{ daarna een re-index uit om te zorgen dat de indexen ook weer goed staan.  }
{ Omdat de re-index routine veel van het werk doet, hernummeren we hier     }
{ alleen de berichten.                                                      }
{                                                                           }
PROCEDURE Jam_RenumberArea (VAR AreaRec : AreaBaseRecord);

VAR JLRFile : FILE;

    PROCEDURE UpdateJLR (OldNum,NewNum : LONGINT);

    VAR LR      : JLRRecord;
        IORes   : BYTE;
        DoWrite : BOOLEAN;

    BEGIN
         { zoek in de JLR file naar overeenkomsten }
         { en pas die dan ook aan.                 }
         Seek (JLRFile,0); { RWI 950317: was seek naar pos. 1 !!}

         {## optimise by using array read/writes}
         WHILE (FilePos (JLRFile) < FileSize (JLRFile)) DO
         BEGIN
              {$I-} BlockRead (JLRFile,LR,SizeOf (JLRRecord)); {$I+} IORes:=IOResult;
              IF (IORes <> 0) THEN
                 Exit;

              IF (LR.UserCRC <> $FFFFFFFF) AND (LR.UserID <> $FFFFFFFF) THEN
              BEGIN
                   DoWrite:=FALSE;

                   IF (LR.LastReadMsg = OldNum) THEN
                   BEGIN
                        LR.LastReadMsg:=NewNum;
                        DoWrite:=TRUE;
                   END;

                   IF (LR.HighReadMsg = OldNum) THEN
                   BEGIN
                        LR.HighReadMsg:=NewNum;
                        DoWrite:=TRUE;
                   END;

                   IF DoWrite THEN
                   BEGIN
                        Seek (JLRFile,FilePos (JLRFile)-SizeOf (JLRRecord));
                        BlockWrite (JLRFile,LR,SizeOf (JLRRecord));
                   END;
              END;
         END; { while }
    END;

    PROCEDURE CheckJLR (LastNum : LONGINT);

    VAR LR      : JLRRecord;
        IORes   : BYTE;
        DoWrite : BOOLEAN;

    BEGIN
         IF (LastNum = 0) THEN
            Exit;

         Seek (JLRFile,0);
         WHILE (FilePos (JLRFile) < FileSize (JLRFile)) DO
         BEGIN
              {$I-} BlockRead (JLRFile,LR,SizeOf (JLRRecord)); {$I+} IORes:=IOResult;
              IF (IORes <> 0) THEN
                 Exit;

              IF (LR.UserCRC <> $FFFFFFFF) AND (LR.UserID <> $FFFFFFFF) THEN
              BEGIN
                   DoWrite:=FALSE;

                   { Note: LastNum assumed > 0 }
                   IF (LR.LastReadMsg > LastNum) THEN
                   BEGIN
                        LogMessage (liGeneral,'[RenumJAM] Bad LASTread in JLR @'+
                                    Longint2String (FilePos (JLRFile)-SizeOf (JLRRecord))+':'+
                                    Longint2String (LR.LastReadMsg)+'), resetting to 1 ('+AreaName+')');

                        LR.LastReadMsg:=1;
                        DoWrite:=TRUE;
                   END;

                   { Note: LastNum assumed > 0 }
                   IF (LR.HighReadMsg > LastNum) THEN
                   BEGIN
                        LogMessage (liGeneral,'[RenumJAM] Bad HIGHread in JLR @'+
                                    Longint2String (FilePos (JLRFile)-SizeOf (JLRRecord))+':'+
                                    Longint2String (LR.HighReadMsg)+'), resetting to 1 ('+AreaName+')');

                        LR.HighReadMsg:=1;
                        DoWrite:=TRUE;
                   END;

                   IF DoWrite THEN
                   BEGIN
                        Seek (JLRFile,FilePos (JLRFile)-SizeOf (JLRRecord));
                        BlockWrite (JLRFile,LR,SizeOf (JLRRecord));
                   END;
              END;

         END; { while }
    END;

{Jam_RenumberArea}

VAR NextMsgNum : LONGINT;
    StorePos   : LONGINT;
    IORes      : BYTE;
    NextEntry  : LONGINT;

LABEL GaVerder;

BEGIN
     { open de database }
     IF (NOT OpenBase (TRUE,AreaRec.AreaName_F,AreaRec.FidoMsgPath)) THEN
     BEGIN
          LogMessage (liFatal,'[JamRenum] Unable to access '+AreaRec.AreaName_F);
          Exit;
     END;

     Assign (JLRFile,AreaRec.FidoMsgPath+EXT_LRDFILE);
     {$I-} Reset (JLRFile,1); {$I+} IORes:=IOResult;
     IF (IORes <> 0) THEN
     BEGIN
          IF (IORes = 2) THEN
          BEGIN
               { probeer de file aan te maken }
               LogMessage (liGeneral,'[RenumJAM] LastRead file does exist; creating '+AreaRec.FidoMsgPath+EXT_LRDFILE);

               {$I-} ReWrite (JLRFile,1); {$I+} IORes:=IOResult;
               IF (IORes <> 0) THEN
               BEGIN
                    LogDiskIOError (IORes,'[RenumJAM] Cannot create above mentioned file');
                    CloseBase;
                    Exit;
               END;
          END ELSE
          BEGIN
               LogDiskIOError (IORes,'[RenumJAM] Cannot open '+AreaRec.FidoMsgPath+EXT_LRDFILE);
               CloseBase;
               Exit;
          END;
     END;

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

     { zorg dat andere programma niet meer kunnen }
     IF (NOT LockBase) THEN
     BEGIN
          LogMessage (liFatal,'[RenumJAM] Area locked tight ('+AreaName+'); renumber failed');
          CloseBase; { RWI 950121 }
          Exit;
     END;

     { overschrijf de indexfile }
     {$I-} ReWrite (IdxFile,1); {$I+} IORes:=IOResult;
     IF (IORes <> 0) THEN
     BEGIN
          LogDiskIOError (IORes,'[RenumJAM] Unable to recreate index file for '+AreaName);
          CloseBase;
          Exit;
     END;

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

     NextMsgNum:=1; { nummer van eerste bericht }

     { zorg voor up-to-date informatie }
     ReadJAMHeader;
     IF (Jam_JHR_header.BaseMsgNum <> NextMsgNum) THEN
     BEGIN
          Jam_JHR_header.BaseMsgNum:=NextMsgNum; { 1-based }
          WriteJAMHeader; { seeked eerst weer naar 0 }
          { dus nu staan we weer op dezelfde plek als na de ReadJamHeader }
     END;

     Status.DezeToDo:=0; { unknown }
     Status.DezeArea:=0;

     WHILE (NOT Eof (HdrFile)) DO
     BEGIN
          StorePos:=FilePos (HdrFile);

          IF (NOT ReadHeaderEntry) THEN
          BEGIN
               { probeer het volgende intacte frame te vinden }
               IF (NOT Synchronize (HdrFile,HeaderSig,SizeOf (HeaderSig))) THEN
               BEGIN
                    LogMessage (liFatal,'[JamRenum] Error synchronizing message header in '+AreaName+', aborting');
                    UnlockBase;
                    CloseBase;
                    Exit;
               END;

               Continue;
          END;

          { nieuwe msg nummer invullen }

          { RWI 950121: controle op deleted verwijderd }
          { RWI 970205: weer toegevoegd. Deleted berichten kregen ook }
          {             een nummer en dat leidde tot gaten!           }
          IF ((Jam_MSG_header.Attribute AND JMSG_DELETED) <> 0) THEN
             GOTO GaVerder;

          IF (Jam_MSG_header.MsgNum <> NextMsgNum) THEN
          BEGIN
               UpdateJLR (Jam_MSG_header.MsgNum,NextMsgNum);

               Jam_MSG_header.MsgNum:=NextMsgNum;

               { lekker lokaal met een eigen routine updaten... }
               Seek (HdrFile,JamMsgHeaderOffset);
               BlockWrite (HdrFile,Jam_MSG_Header,SizeOf (Jam_MSG_Header));
          END;

          Inc (NextMsgNum);

          { RWI 970205: we moeten de index ook updaten }

          { Als het bericht nummer groter is dan het vorige bericht nummer }
          { vul de header aan met lege records.                            }
          NextEntry:=(Jam_Msg_Header.MsgNum-Jam_JHR_header.BaseMsgNum)*SizeOf (JAMIDXREC);

          { voeg een index entry toe }
          IF (NOT WriteIndexEntry (ReadJAMSubField0003,JamMsgHeaderOffset)) THEN
             LogMessage (liFatal,'[RenumJAM] Unable to append to index file '+AreaName);


     GaVerder:

          { subfields blok overslaan, klaar zetten voor volgende header }
          Seek (HdrFile,JamMsgHeaderOffset+SizeOf (Jam_MSG_Header)+Jam_MSG_Header.SubFieldLen);

          Inc (Status.DezeArea);
          IF ((Status.DezeArea MOD 24) = 1) THEN
          BEGIN
               UtilUpdateProgress;
               Slice_Now;
          END;

     END; { while }

     { controleer of de JLRfile illegale pointers bevat. Zoja, reset ze dan }
     CheckJLR (NextMsgNum-1);

     { sluit de JLR base }
     {$IFDEF LogFileIO}PreCloseF (JLRFile);{$ENDIF}
     Close (JLRFile);

     { geef de base weer vrij voor gebruik }
     UnlockBase;
     CloseBase;
END;


{--------------------------------------------------------------------------}
{ Jam_Init                                                                 }
{                                                                          }
{ Doet een reset van de interne variabelen van de Jam Base.                }
{                                                                          }
PROCEDURE Jam_Init;
BEGIN
     AreaName:='';
     AreaPath:='';
     IsOpen:=FALSE;
     ImportMsgs:=TRUE;
     TouchCounter:=0;

     JamSubFieldList.Init (SizeOf (JAMSUBFIELD),NIL);
     TotalSubFieldSize:=0;
END;


{--------------------------------------------------------------------------}
{ Jam_Done                                                                 }
{                                                                          }
{ Zorgt ervoor dat de file handles aan het einde ook nog worden vrij       }
{ gegeven.                                                                 }
{                                                                          }
PROCEDURE Jam_Done;
BEGIN
     CloseBase;
END;


{--------------------------------------------------------------------------}
{ Jam_Rescan                                                               }
{                                                                          }
{ This routine scans the given JAM base for the RESCAN procedure. Most of  }
{ the code here was snatched from the normal Jam_ScanArea routine. This    }
{ routine is called for Echomail areas only.                               }
{                                                                          }
PROCEDURE Jam_Rescan (VAR AreaData : AreaBaseRecord);

VAR BodyFile    : FBufferType;
    DatumTijd   : DateTime;
    BytesToRead : LONGINT;
    Regel,
    LastRegel   : STRING;
    RealLen     : BYTE;
    TrashCount  : WORD;
    IORes       : BYTE;
    OldIdxPos   : LONGINT;
    OldHdrPos   : LONGINT;
    PrevTouch   : BYTE;
    Result      : RescanResultType;

LABEL GaVerder;

BEGIN
     IF (NOT OpenBase (FALSE,AreaData.AreaName_F,AreaData.FidoMsgPath)) THEN
     BEGIN
          LogMessage (liFatal,'[JAM] Unable to access '+AreaData.AreaName_F);
          Exit;
     END;

     ReadJamHeader;     { zorg dat we verse informatie hebben }
     ImportMsgs:=TRUE;  { zorg dat we alles lezen }

     { zoekt in de index en geeft de pointer naar het eerste bericht }
     IF (NOT GetFirstMessage) THEN
     BEGIN
          { no messages in the base }
          CloseBase;
          Exit;
     END;

     REPEAT
           Result:=rstNoExport;

           { Lees de bericht header en subfields in het geheugen }
           IF (NOT ReadHeaderEntry) THEN
           BEGIN
                LogMessage (liFatal,'[JAM] Error reading message header for '+AreaName+' in msg '+
                            Word2String (Currentmessage)+', possible index failure?');
                CloseBase;
                Exit;
           END;

           IF ((Jam_MSG_header.Attribute AND JMSG_DELETED) <> 0) THEN
              GOTO GaVerder;

           { check if rescan wants this message }
           Result:=Rescan_ExportCheck (Jam_MSG_header.DateWritten);

           IF (Result <> rstExport) THEN
              GOTO Gaverder;

           { do not export msgs with the HOLD or LOCKED flag }
           IF ((Jam_MSG_header.Attribute AND JMSG_HOLD) <> 0) OR
              ((Jam_MSG_header.Attribute AND JMSG_LOCKED) <> 0)
           THEN
               GOTO GaVerder;

           MsgsEmpty;

           Msg.Ready_F:=Local_Echomail;
           Msg.Area_F:=AreaData.AreaName_F;

           { als er subfields waren, lees die dan ook in }
           IF (Jam_MSG_Header.SubFieldLen > 0) THEN
              ReadJAMSubFields (ImportMsgs);

           { converteer de Jam_Header naar de interne structuur }
           Msg.Attr_F:=AttrJam2Ftn;

           { read the message }

           { converteer de JAM Header naar de fido stijl header }
           UnixToDos (Jam_MSG_header.DateWritten,DatumTijd);

           { stel de datum in }
           WITH DatumTijd DO
                Msg.Date_F:=FidoTime2Str (Day,Month,Year,Hour,Min,Sec);

           Msg.Cost_F:=jam_MSG_header.Cost;

           { open de invoer file }
           IF (NOT FBufferOpen (BodyFile,AreaPath+EXT_TXTFILE,10000,0)) THEN
           BEGIN
                LogMessage (liFatal,'[JAM] Unable to open area '+AreaName);
                FBufferClose (BodyFile);
                CloseBase;
                Exit;
           END;

           IF (NOT FBSeek (BodyFile,Jam_MSG_header.TxtOffset)) THEN
           BEGIN
                LogMessage (liFatal,'[JAM] Error seeking position '+Longint2String (Jam_MSG_header.TxtOffset)+
                            ' in '+AreaPath+EXT_TXTFILE);
                FBufferClose (BodyFile);
                CloseBase;
                Exit;
           END;

           BytesToRead:=Jam_MSG_header.TxtLen;

           Found_SeenBy:=FALSE;
           Found_Path:=FALSE;
           Found_Origin:=FALSE;
           Found_Tear:=FALSE;

           WHILE (BytesToRead > 0) AND
                 FBReadLnCRTell (BodyFile,Regel,TrashCount) AND
                 (Regel <> #0) DO
           BEGIN
                IF (BytesToRead = 1) AND (TrashCount > 0) THEN
                   Break; { reached end of message }

                Dec (BytesToRead,Length (Regel)+TrashCount);

                { filter alle soft CR's eruit }
                WHILE (Pos (#$8D,Regel) > 0) DO
                      Delete (Regel,Pos (#$8D,Regel),1);

                FidoAddLineToMessage (Regel,LastRegel);
           END; { while }

           IF (LastRegel[Length (LastRegel)] <> #13) AND (PrevKludgeID <> klNone) THEN
              MsgsAddLineToNoEOL (Footer_F,#13);

           FidoAddLastLine (LastRegel);

           FBufferClose (BodyFile);

           { add origin }
           FidoFinishEchomailExport (AreaData);

           PrevTouch:=TouchCounter;
           OldIdxPos:=FilePos (IdxFile);
           OldHdrPos:=CurrentHeader;

           Rescan_DeliverMessage;

           IF (TouchCounter <> PrevTouch) THEN
           BEGIN
                { Er is iets gebeurt met de JAM-base object instantie. }
                { Nu moeten we de boel herstellen.                     }

                IF (NOT OpenBase (FALSE,AreaData.AreaName_F,AreaData.FidoMsgPath)) THEN
                BEGIN
                     LogMessage (liFatal,'[JAM] Unable to re-access '+AreaData.AreaName_F);
                     Exit;
                END;

                ReadJamHeader;     { zorg dat we verse informatie hebben }
                ImportMsgs:=TRUE;  { zorg dat we alles lezen }

                { herstel nu de file pointer in de index file, want die }
                { gebruikt GetNextMessage.                              }
                Seek (IdxFile,OldIdxPos);
           END;

GaVerder:

     UNTIL (Result = rstNoExport_Stop) OR (NOT GetNextMessage);

     CloseBase;
     MsgsEmpty;
END;


{--------------------------------------------------------------------------}
{ Unit Initialization                                                      }
{                                                                          }
BEGIN
     Jam_Init;
END.

