{MKMsgJam - JAM Msg Object Unit
Copyright 1993 by Mark May (1:110/290;maym@dmapub.dma.org)
Changes (c) 1999-2000 by Andre Grueneberg (2:2411/525;andre@grueneberg.de)
Changes (c) 1999 by Vadim Rumyantsev (2:5030/48.4)
Changes (c) 1998-2000 by Bernhard R. Link (2:2476/841.64;brl@gmx.de)
Changes (c) 2001 by Oliver Kopp (2:2471/1464;olly98@users.sourceforge.net)
****************************************************************************
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
****************************************************************************}

(* $Id: mkmsgjam.pas,v 1.10 2001/04/15 16:35:43 olly98 Exp $ *)

Unit MKMsgJam;

{
     Thanks to the following for creating the JAM message base format


     JAM(mbp) - Copyright 1993 Joaquim Homrighausen, Andrew Milner,
                               Mats Birch, Mats Wallin.
                               ALL RIGHTS RESERVED.
}

{
     The JAM developers have requested that you include the above
     copyright notice in any JAM products you develop
}

{$I platform.inc}
{$I mkglobal.inc}

Interface

Uses MKMsgAbs, FtnAddr, uDate, aString,
{$IFDEF VIRTUALPASCAL}
  use32,
{$ENDIF}
{$IFDEF WINDOWS}
  Strings, WinDos;
{$ELSE}
  Dos;
{$ENDIF}


Type JamHdrType = Record
  Signature: Array[1..4] of Char;
  Created: LongInt;
  ModCounter: LongInt;
  ActiveMsgs: LongInt;
  PwdCRC: LongInt;
  BaseMsgNum: LongInt;
  Extra: Array[1..1000] of Char;
  End;


Type JamMsgHdrType = Record
  Signature: Array[1..4] of Char;
  Rev: System.Word;
  Resvd: System.Word;
  SubFieldLen: LongInt;
  TimesRead: LongInt;
  MsgIdCrc: LongInt;
  ReplyCrc: LongInt;
  ReplyTo: LongInt;
  ReplyFirst: LongInt;
  ReplyNext: LongInt;
  DateWritten: LongInt;
  DateRcvd: LongInt;
  DateArrived: LongInt;
  MsgNum: LongInt;
  Attr1: LongInt;
  Attr2: LongInt;
  TextOfs: LongInt;
  TextLen: LongInt;
  PwdCrc: LongInt;
  Cost: LongInt;
  End;


Type JamIdxType = Record
  MsgToCrc: LongInt;
  HdrLoc: LongInt;
  End;


Type JamLastType = Record
  NameCrc: LongInt;
  UserNum: LongInt;
  LastRead: LongInt;
  HighRead: LongInt;
  End;


Const JamIdxBufSize = {$IFNDEF VirtualPascal}500{$ELSE}4000{$ENDIF};


Type JamIdxArrayType = Array[0..JamIdxBufSize] of JamIdxType;


Const JamSubBufSize = {$IFNDEF VirtualPascal}4000{$ELSE}10000{$ENDIF};


Type JamSubBuffer = Array[1..JamSubBufSize] of Char;


Const JamTxtBufSize = {$IFNDEF VirtualPascal}16*1024{$ELSE}128*1024{$ENDIF};

Const TxtSubBufSize = 2000;            {Note actual size is one greater}


Type JamTxtBufType = Array[0..JamTxtBufSize] Of Char;

Type HdrType = Record
  JamHdr: JamMsgHdrType;
  SubBuf: JamSubBuffer;
  End;

Type PStrList=^TStrList;
  TStrList=Record
  s: String;
  n: PStrList;
  End;


Type JamMsgType = Record
  HdrFile: File;
  TxtFile: File;
  IdxFile: File;
  MsgPath: String[128];
  BaseHdr: JamHdrType;
  Dest: AddrType;
  Orig: AddrType;
  MsgFrom: String[65];
  MsgTo: String[65];
  MsgSubj: String[100];
  MsgDate: String[8];
  MsgTime: String[8];
  CurrMsgNum: LongInt;
  YourName: String[35];
  YourHdl: String[35];
  NameCrc: LongInt;
  HdlCrc: LongInt;
  TxtPos: LongInt; {TxtPos < 0 means get from sub text}
  TxtEnd: LongInt;
  TxtBufStart: LongInt;
  TxtRead: Word;
  MailType: MsgMailType;
  BufFile: File;
  LockCount: LongInt;
  IdxStart: LongInt;
  IdxRead: Word;
  TxtSubBuf: Array[0..TxtSubBufSize] of Char; {temp storage for text on subfields}
  TxtSubChars: Integer;
  AttList: PStrList;
  ReqList: PStrList;
  End;



Type JamMsgObj = Object (AbsMsgObj)
  JM: ^JamMsgType;
  MsgHdr: ^HdrType;
  JamIdx: ^JamIdxArrayType;
  TxtBuf: ^JamTxtBufType;
  Error: Word;

  Constructor Init;                      {Initialize}
  Destructor Done; Virtual; {Done}

  Function  OpenMsgBase: Word; Virtual;
  Function  CloseMsgBase: Word; Virtual;
  Function  CreateMsgBase(MaxMsg: Word; MaxDays: Word): Word; Virtual;

  Procedure SetMsgBasePath(Const St: OpenString); Virtual; {Set path to messagebase}
  Procedure SetMsgBaseType(MT: MsgMailType); Virtual; {Set message base type}

  Function  MsgBaseExists: Boolean; Virtual; {Does msg base exist}

  Function  LockMsgBase: Boolean; virtual;
  Function  UnLockMsgBase: Boolean; virtual;

  Function  NumberOfMsgs: LongInt; Virtual; {Number of messages}

  Function  GetHighMsgNum: LongInt; Virtual; {Get highest mail msg number in area}

  Function KillMsg(MsgNum: LongInt):Integer; virtual{$IFDEF UA};abstract{$ENDIF}; {Kill msg msgnum}

  (* only stubs, because jam needs UCRC , which is not provided by mkmsgabs *)
  Function  GetLastRead(UNum: LongInt):Longint; virtual{$IFDEF UA};abstract{$ENDIF}; {Get last read for user num}
  Procedure SetLastRead(UNum: LongInt; LR: LongInt); virtual{$IFDEF UA};abstract{$ENDIF}; {Set last read}

  Function  SeekFirst(MsgNum: LongInt;reload:Boolean): Boolean; Virtual; {Seek msg number, reload isn't evaluated}

  Function  SeekPrior: Boolean; Virtual; {Seek prior matching msg}
  Function  SeekNext: Boolean; Virtual; {Find next matching msg}

  Function  YoursFirst(Const Name, Handle: OpenString): Boolean; Virtual; {Seek your mail}
  Function  YoursNext: Boolean; Virtual; {Seek next your mail}

  Function  GetMsgNum: LongInt; Virtual; {Get message number}

  Function  GetTxtPos: LongInt; Virtual; {Get indicator of msg text position}
  Procedure SetTxtPos(TP: LongInt); Virtual; {Set text position}

  Function  MsgStartUp: Boolean; Virtual; {set up msg for reading}
  Function  MsgTxtStartUp: Boolean; Virtual; {Do message text start up tasks}
  Function  GetChar: Char; Virtual;
  Function  EOM: Boolean; Virtual; {No more msg text}

  Function  GetSubj: TString; Virtual; {Get subject on current msg}
  Function  GetFromName: TString; Virtual; {Get from name on current msg}
  Function  GetFromAddr: TFtnAddr; Virtual; {Get origin address}
  Function  GetToName: TString; Virtual; {Get to name on current msg}
  Function  GetToAddr: TFtnAddr; Virtual; {Get destination address}

  Function  GetDateTime:TDateTimeStr; virtual;

  Function  GetSeeAlso: LongInt; Virtual; {Get see also of current msg}
  Function  GetNextSeeAlso: LongInt; Virtual;
  Function  GetRefer: LongInt; Virtual; {Get reply to of current msg}

  Function  GetCost: Word; Virtual; {Get cost of current msg}

  Function  GetFAttach: String; Virtual;
  Function  GetFRequest: String; Virtual;
  Function  IsFAttach: Boolean; Virtual; {Is current msg file attach}
  Function  IsFileReq: Boolean; Virtual; {Is current msg a file request}

  Function  IsCrash: Boolean; Virtual; {Is current msg crash}
  Function  IsDeleted: Boolean; Virtual; {Is current msg deleted}
  Function  IsEchoed: Boolean; Virtual; {Msg should be echoed, always returns TRUE}
  Function  IsLocal: Boolean; Virtual; {Is current msg local}
  Function  IsFwd: Boolean; Virtual; {Is current msg in transit}
  Function  IsHold: Boolean; virtual; {Is current msg ist on hold, always returns FALSE}
  Function  IsKillSent: Boolean; Virtual; {Is current msg kill sent}
  Function  IsPriv: Boolean; Virtual; {Is current msg priviledged/private}
  Function  IsRcvd: Boolean; Virtual; {Is current msg received}
  Function  IsReqAud: Boolean; Virtual; {Is current msg request audit}
  Function  IsReqRct: Boolean; Virtual; {Is current msg request receipt}
  Function  IsRetRct: Boolean; Virtual; {Is current msg a return receipt, always returns FALSE}
  Function  IsSent: Boolean; Virtual; {Is current msg sent}

  Function  StartNewMsg: Boolean; Virtual;
  Function  WriteMsg: Word; Virtual;
  Function  ReWriteHdr: Word; Virtual; {Rewrite msg header after changes}

  Procedure DoChar(Ch: Char); Virtual; {Add character to message text}
  Procedure DoKludgeLn(const KludgeStr: OpenString); Virtual; {Add ^AKludge line to msg}

  Procedure SetSubj(Const Str: OpenString); Virtual; {Set message subject}
  Procedure SetFromName(Const Name: OpenString); Virtual; {Set message from}
  Procedure SetFromAddr(Const Addr: TFtnAddr); Virtual; {Set Zone/Net/Node/Point for Orig}
  Procedure SetToName(Const Name: OpenString); Virtual; {Set message to}
  Procedure SetToAddr(Const Addr: TFtnAddr); Virtual; {Set Zone/Net/Node/Point for Dest}

  Procedure SetDateTime(const s:TDateTimeStr);virtual;

  Procedure SetSeeAlso(SAlso: LongInt); Virtual; {Set message see also}
  Procedure SetNextSeeAlso(SAlso: LongInt); Virtual;
  Procedure SetRefer(SRefer: LongInt); Virtual; {Set message reference}

  Procedure SetCost(SCost: Word); Virtual; {Set message cost}

  Procedure AddFAttach(const s: String); Virtual;
  Procedure AddFRequest(const s: String); Virtual;
  Procedure SetFAttach(St: Boolean); Virtual; {Set file attach status}
  Procedure SetFileReq(St: Boolean); Virtual; {Set file request status}

  Procedure SetCrash(St: Boolean); Virtual; {Set crash netmail status}
  Procedure SetEcho(ES: Boolean); virtual;
  Procedure SetLocal(LS: Boolean); Virtual; {Set local status}
  Procedure SetFwd(St: Boolean); Virtual; {Set in transit status}
  Procedure SetHold(St: Boolean); virtual; {Set file hold status}
  Procedure SetKillSent(St: Boolean); Virtual; {Set kill/sent netmail status}
  Procedure SetPriv(PS: Boolean); Virtual; {Set priveledge vs public status}
  Procedure SetRcvd(RS: Boolean); Virtual; {Set received status}
  Procedure SetReqAud(St: Boolean); Virtual; {Set request audit status}
  Procedure SetReqRct(St: Boolean); Virtual; {Set request receipt status}
  Procedure SetRetRct(St: Boolean); Virtual; {Set return receipt status, does nothing!}
  Procedure SetSent(St: Boolean); Virtual; {Set sent netmail status}

  (* new *)

  (* JAM needs UCRC *)
  Function  GetLastRead_UCRC(UNum, UCRC: LongInt): LongInt; Virtual; {Get last read for user num}
  Procedure SetLastRead_UCRC(UNum, UCRC: LongInt; LR: LongInt); Virtual; {Set last read}

  Procedure AddTxtSub(Const St: String);

  Function  GetMsgLoc: LongInt; Virtual; {Msg location}
  Procedure SetMsgLoc(ML: LongInt); Virtual; {Msg location}

  Procedure SetAttr1(Mask: LongInt; St: Boolean); {Set attribute 1}
  Function  ReadIdx: Word;
  Function  WriteIdx: Word;
  Procedure AddSubField(id: Word; Data: String);
  Function  FindLastRead(Var LastFile: File; UNum: LongInt): LongInt;
  Function  ReReadIdx(Var IdxLoc : LongInt) : Word;

  private
    Function  SeekFound: Boolean; Virtual;
  End;


Type JamMsgPtr = ^JamMsgObj;


Function JamStrCrc(Const St: String): LongInt;


Implementation

Uses uFiles, Crc32, uString;

Const
  Jam_Local =        $00000001;
  Jam_InTransit =    $00000002;
  Jam_Priv =         $00000004;
  Jam_Rcvd =         $00000008;
  Jam_Sent =         $00000010;
  Jam_KillSent =     $00000020;
  Jam_AchvSent =     $00000040;
  Jam_Hold =         $00000080;
  Jam_Crash =        $00000100;
  Jam_Imm =          $00000200;
  Jam_Direct =       $00000400;
  Jam_Gate =         $00000800;
  Jam_Freq =         $00001000;
  Jam_FAttch =       $00002000;
  Jam_TruncFile =    $00004000;
  Jam_KillFile =     $00008000;
  Jam_RcptReq =      $00010000;
  Jam_ConfmReq =     $00020000;
  Jam_Orphan =       $00040000;
  Jam_Encrypt =      $00080000;
  Jam_Compress =     $00100000;
  Jam_Escaped =      $00200000;
  Jam_FPU =          $00400000;
  Jam_TypeLocal =    $00800000;
  Jam_TypeEcho =     $01000000;
  Jam_TypeNet =      $02000000;
  Jam_NoDisp =       $20000000;
  Jam_Locked =       $40000000;
  Jam_Deleted =      $80000000;


Type SubFieldType = Record
  LoId: System.Word;
  HiId: System.Word;
  DataLen: LongInt;
  Data: Array[1..1000] of Char;
  End;


Constructor JamMsgObj.Init;
  Begin
  New(JM);
  New(JamIdx);
  New(MsgHdr);
  New(TxtBuf);
  If (JM = Nil) Or (JamIdx = Nil) or (MsgHdr = Nil) or (TxtBuf = Nil) Then
    Begin
      Dispose(JM);
      Dispose(JamIdx);
      Dispose(MsgHdr);
      Dispose(TxtBuf);
    Fail;
    Exit;
    End
  Else
    Begin;
    FillChar(JM^, SizeOf(JM^), #0);
    JM^.MsgPath := '';
    JM^.IdxStart := -30;
    JM^.IdxRead := 0;
    Error := 0;
    End;
  End;


Destructor JamMsgObj.Done;
  Var
    AktPtr      : PStrList;

  Begin
  While Not (JM^.AttList = Nil) Do
    Begin
    AktPtr := JM^.AttList^.n;
    Dispose(JM^.AttList);
    JM^.AttList := AktPtr;
    End;
  While Not (JM^.ReqList = Nil) Do
    Begin
    AktPtr := JM^.ReqList^.n;
    Dispose(JM^.ReqList);
    JM^.ReqList := AktPtr;
    End;
    Dispose(JM);
    Dispose(JamIdx);
    Dispose(MsgHdr);
    Dispose(TxtBuf);
  End;


Function JamStrCrc(Const St: String): LongInt;
  Var
    i: Word;
    crc: LongInt;

  Begin
  Crc := -1;
  For i := 1 to Length(St) Do
    Crc := Updc32(Byte(DownCase(St[i])), Crc);
  JamStrCrc := Crc;
  End;


Procedure JamMsgObj.SetMsgBasePath(Const St: OpenString);
  Var
    Path: PathStr;
    Name: NameStr;
    Ext: ExtStr;

  Begin
    FSplit(FExpand(St), Path, Name, Ext);

    (* longname support - like f:\mail\fidosoft.ger *)
    if (Length(Ext)<>4) or (UpCase(Ext[2])<>'J') then begin
      (* given St was a longname *)
      JM^.MsgPath:=Path + Name + Ext;
    end else begin
      JM^.MsgPath := Path + Name;
    end;
  End;


Function JamMsgObj.GetHighMsgNum: LongInt;
  Begin
  GetHighMsgNum := JM^.BaseHdr.BaseMsgNum + FileSize(JM^.IdxFile) - 1;
  End;


Procedure JamMsgObj.SetToAddr(Const Addr: TFtnAddr);
  Begin
  JM^.Dest := Addr;
  End;


Procedure JamMsgObj.SetFromAddr(Const Addr: TFtnAddr);
  Begin
  JM^.Orig := Addr;
  End;


Procedure JamMsgObj.SetFromName(Const Name: OpenString);
  Begin
  JM^.MsgFrom := Name;
  End;


Procedure JamMsgObj.SetToName(Const Name: OpenString);
  Begin
  JM^.MsgTo := Name;
  End;


Procedure JamMsgObj.SetSubj(Const Str: OpenString);
  Begin
  JM^.MsgSubj := Str;
  End;


Procedure JamMsgObj.SetCost(SCost: Word);
  Begin
  MsgHdr^.JamHdr.Cost := SCost;
  End;


Procedure JamMsgObj.SetRefer(SRefer: LongInt);
  Begin
  MsgHdr^.JamHdr.ReplyTo := SRefer;
  End;


Procedure JamMsgObj.SetSeeAlso(SAlso: LongInt);
  Begin
  MsgHdr^.JamHdr.ReplyFirst := SAlso;
  End;


Procedure JamMsgObj.SetDateTime(Const S: TDateTimeStr);
  Begin
  JM^.MsgDate := Convert_DateTimeStr_To_MMDDYY(S);
  JM^.MsgTime := copy(s, 14, 8);
  End;

Procedure JamMsgObj.SetAttr1(Mask: LongInt; St: Boolean);
  Begin
  If St Then
    MsgHdr^.JamHdr.Attr1 := MsgHdr^.JamHdr.Attr1 Or Mask
  Else
    MsgHdr^.JamHdr.Attr1 := MsgHdr^.JamHdr.Attr1 And (Not Mask);
  End;



Procedure JamMsgObj.SetLocal(LS: Boolean);
  Begin
  SetAttr1(Jam_Local, LS);
  End;


Procedure JamMsgObj.SetRcvd(RS: Boolean);
  Begin
  SetAttr1(Jam_Rcvd, RS);
  End;


Procedure JamMsgObj.SetPriv(PS: Boolean);
  Begin
  SetAttr1(Jam_Priv, PS);
  End;


Procedure JamMsgObj.SetCrash(St: Boolean);
  Begin
  SetAttr1(Jam_Crash, St);
  End;

Procedure JamMsgObj.SetKillSent(St: Boolean);
  Begin
  SetAttr1(Jam_KillSent, St);
  End;


Procedure JamMsgObj.SetSent(St: Boolean);
  Begin
  SetAttr1(Jam_Sent, St);
  End;


Procedure JamMsgObj.SetFAttach(St: Boolean);
  Begin
  SetAttr1(Jam_FAttch, St);
  End;


Procedure JamMsgObj.SetReqRct(St: Boolean);
  Begin
  SetAttr1(Jam_RcptReq, St);
  End;


Procedure JamMsgObj.SetReqAud(St: Boolean);
  Begin
  SetAttr1(Jam_ConfmReq, St);
  End;


Procedure JamMsgObj.SetFileReq(St: Boolean);
  Begin
  SetAttr1(Jam_Freq, St);
  End;


Procedure JamMsgObj.DoChar(Ch: Char);
  Var
    TmpStr: String;
    NumWrite: Word;

  Begin
  Case ch of
    #13: LastSoft := False;
    #10:;
    Else
      LastSoft := True;
    End;
  If (JM^.TxtPos - JM^.TxtBufStart) >= JamTxtBufSize Then
    Begin
    If JM^.TxtBufStart = 0 Then
      Begin
      GetDir(0, TmpStr);
      TmpStr := GetTempName(TmpStr);
      Assign(JM^.BufFile, TmpStr);
      FileMode := fmReadWrite + fmDenyNone;
      ReWrite(JM^.BufFile, 1);
      End;
    NumWrite := JM^.TxtPos - JM^.TxtBufStart;
    BlockWrite(JM^.BufFile, TxtBuf^, NumWrite);
    Error := IoResult;
    JM^.TxtBufStart := FileSize(JM^.BufFile);
    End;
  TxtBuf^[JM^.TxtPos - JM^.TxtBufStart] := Ch;
  Inc(JM^.TxtPos);
  End;


Procedure JamMsgObj.DoKludgeLn(const KludgeStr: OpenString);
  Var
    Str,
    TmpStr: String;

  Begin
  Str:=KludgeStr;
  If Str[1] = #1 Then
    Str := Copy(Str,2,255);
  If Copy(Str,1,5) = 'PID: ' Then
    Begin
    TmpStr := Copy(TrimStr(Copy(Str,5,255)),1,40);
    AddSubField(7, TmpStr);
    End
  Else If Copy(Str,1,7) = 'MSGID: ' Then
    Begin
    TmpStr := Copy(TrimStr(Copy(Str,7,255)),1,100);
    AddSubField(4, TmpStr);
    MsgHdr^.JamHdr.MsgIdCrc := JamStrCrc(TmpStr);
    End
  Else If Copy(Str,1,5) = 'INTL ' Then  {ignore}
  Else If Copy(Str,1,5) = 'TOPT ' Then  {ignore}
  Else If Copy(Str,1,5) = 'FMPT ' Then  {ignore}
  Else If Copy(Str,1,7) = 'REPLY: ' Then
    Begin
    TmpStr := Copy(TrimStr(Copy(Str,7,255)),1,100);
    AddSubField(5, TmpStr);
    MsgHdr^.JamHdr.ReplyCrc := JamStrCrc(TmpStr);
    End
  Else If Copy(Str,1,6) = 'PATH: ' Then
    AddSubField(2002, TrimStr(Copy(Str,7,255)))
  Else If Copy(Str,1,6) = 'FLAGS ' Then
    AddSubField(2003, TrimStr(Copy(Str,7,255)))
  Else
    AddSubField(2000, TrimStr(Str));
  End;


Procedure JamMsgObj.AddSubField(id: Word; Data: String);

  Var
    SubField: ^SubFieldType;

  Begin
  If Data = '' Then
    Exit;
  SubField := @MsgHdr^.SubBuf[MsgHdr^.JamHdr.SubFieldLen + 1];
  If (MsgHdr^.JamHdr.SubFieldLen + 8 + Length(Data) < JamSubBufSize) Then
    Begin
    Inc(MsgHdr^.JamHdr.SubFieldLen, 8 + Length(Data));
    SubField^.LoId := Id;
    SubField^.HiId := 0;
    SubField^.DataLen := Length(Data);
    Move(Data[1], SubField^.Data[1], Length(Data));
    End;
  End;


Function  JamMsgObj.WriteMsg: Word;
  Var
    WriteError: Word;
    i: Word;
    TmpIdx: JamIdxType;

  Begin
  WriteError := 0;
  If LastSoft Then
    Begin
    DoChar(#13);
    DoChar(#10);
    End;
  If WriteError = 0 Then
    Begin
    MsgHdr^.JamHdr.Signature[1] := 'J';{Set signature}
    MsgHdr^.JamHdr.Signature[2] := 'A';
    MsgHdr^.JamHdr.Signature[3] := 'M';
    MsgHdr^.JamHdr.Signature[4] := #0;
    Case JM^.MailType of
      mtLocal: SetAttr1(Jam_TypeLocal, True);
      mtEcho: SetAttr1(Jam_TypeEcho, True);
      mtNetMail: SetAttr1(Jam_TypeNet, True);
      End;
    MsgHdr^.JamHdr.Rev := 1;
    MsgHdr^.JamHdr.DateArrived := DosDateToUnixDate(GetDosDate); {Get date processed}
    MsgHdr^.JamHdr.DateWritten := TDateTimeStrToUnixDate(GetDateTime);
    End;
  If WriteError = 0 Then
    Begin                              {Lock message base for update}
    If Not LockMsgBase Then
      WriteError := 5;
    End;
  If WriteError = 0 Then
    Begin                              {Handle message text}
    MsgHdr^.JamHdr.TextOfs := FileSize(JM^.TxtFile);
    MsgHdr^.JamHdr.MsgNum := GetHighMsgNum + 1;
    MsgHdr^.Jamhdr.TextLen := JM^.TxtPos;
    If JM^.TxtBufStart > 0 Then
      Begin                            {Write text using buffer file}
      i := JM^.TxtPos - JM^.TxtBufStart;
      BlockWrite(JM^.BufFile, TxtBuf^, i); {write buffer to file}
      WriteError := IoResult;
      If WriteError = 0 Then           {seek start of buffer file}
        Begin
        Seek(JM^.BufFile, 0);
        WriteError := IoResult;
        End;
      If WriteError = 0 Then           {seek end of text file}
        Begin
        Seek(JM^.TxtFile, FileSize(JM^.TxtFile));
        WriteError := IoResult;
        End;
      While ((Not Eof(JM^.BufFile)) and (WriteError = 0)) Do
        Begin                          {copy buffer file to text file}
        BlockRead(JM^.BufFile, TxtBuf^, SizeOf(TxtBuf^), i);
        WriteError := IoResult;
        If WriteError = 0 Then
          Begin
          JM^.TxtBufStart := FilePos(JM^.TxtFile);
          JM^.TxtRead := i;
          BlockWrite(JM^.TxtFile, TxtBuf^, i);
          Error := IoResult;
          End;
        End;
      Close(JM^.BufFile);
      Error := IoResult;
      Erase(JM^.BufFile);
      Error := IoResult;
      End
    Else
      Begin                            {Write text using TxtBuf only}
      Seek(JM^.Txtfile, FileSize(JM^.TxtFile));
      WriteError := IoResult;
      If WriteError = 0 Then
        Begin
        BlockWrite(JM^.TxtFile, TxtBuf^, JM^.TxtPos);
        WriteError := IoResult;
        JM^.TxtRead := JM^.TxtPos;
        End;
      End;
    If WriteError = 0 Then             {Add index record}
      Begin
      TmpIdx.HdrLoc := FileSize(JM^.HdrFile);
      TmpIdx.MsgToCrc := JamStrCrc(JM^.MsgTo);
      Seek(JM^.IdxFile, FileSize(JM^.IdxFile));
      WriteError := IoResult;
      End;
    If WriteError = 0 Then             {write index record}
      Begin
      BlockWrite(JM^.IdxFile, TmpIdx, 1);
      WriteError := IoResult;
      End;
    If WriteError = 0 Then
      Begin                            {Add subfields as needed}
      If Length(JM^.MsgTo) > 0 Then
        AddSubField(3, JM^.MsgTo);
      If Length(JM^.MsgFrom) > 0 Then
        AddSubField(2, JM^.MsgFrom);
      If Length(JM^.MsgSubj) > 0 Then
        AddSubField(6, JM^.MsgSubj);
      If ((JM^.Dest.Zone <> 0) or (JM^.Dest.Net <> 0) or
        (JM^.Dest.Node <> 0) or (JM^.Dest.Point <> 0)) Then
        AddSubField(1, AddrStr(JM^.Dest));
      If ((JM^.Orig.Zone <> 0) or (JM^.Orig.Net <> 0) or
        (JM^.Orig.Node <> 0) or (JM^.Orig.Point <> 0)) Then
        AddSubField(0, AddrStr(JM^.Orig));
      Seek(JM^.HdrFile, FileSize(JM^.HdrFile)); {Seek to end of .jhr file}
      WriteError := IoResult;
      End;
    If WriteError = 0 Then
      Begin                            {write msg header}
      BlockWrite(JM^.HdrFile, MsgHdr^, SizeOf(MsgHdr^.JamHdr) +
        MsgHdr^.JamHdr.SubFieldLen);
      WriteError := IoResult;
      End;
    If WriteError = 0 Then
      Begin                            {update msg base header}
      Inc(JM^.BaseHdr.ActiveMsgs);
      Inc(JM^.BaseHdr.ModCounter);
      End;
    If UnLockMsgBase Then;             {unlock msg base}
    End;
  WriteMsg := WriteError;              {return result of writing msg}
  End;


Function JamMsgObj.GetChar: Char;
  Begin
  If JM^.TxtPos < 0 Then
    Begin
    GetChar := JM^.TxtSubBuf[JM^.TxtSubChars + JM^.TxtPos];
    Inc(JM^.TxtPos);
    If JM^.TxtPos >= 0 Then
      JM^.TxtPos := MsgHdr^.JamHdr.TextOfs;
    End
  Else
    Begin
    If ((JM^.TxtPos < JM^.TxtBufStart) Or
    (JM^.TxtPos >= JM^.TxtBufStart + JM^.TxtRead)) Then
      Begin
      JM^.TxtBufStart := JM^.TxtPos - 80;
      If JM^.TxtBufStart < 0 Then
        JM^.TxtBufStart := 0;
      Seek(JM^.TxtFile, JM^.TxtBufStart);
      Error := IoResult;
      If Error = 0 Then
        Begin
        BlockRead(JM^.TxtFile, TxtBuf^, SizeOf(TxtBuf^), JM^.TxtRead);
        Error := IoResult;
        End;
      End;
    GetChar := TxtBuf^[JM^.TxtPos - JM^.TxtBufStart];
    Inc(JM^.TxtPos);
    End;
  End;



Procedure JamMsgObj.AddTxtSub(Const St: String);
  Var
    i: Word;

  Begin
  For i := 1 to Length(St) Do
    Begin
    If JM^.TxtSubChars <= TxtSubBufSize Then
      Begin
      JM^.TxtSubBuf[JM^.TxtSubChars] := St[i];
      Inc(JM^.TxtSubChars);
      End;
    End;
  If JM^.TxtSubChars <= TxtSubBufSize Then
    Begin
    JM^.TxtSubBuf[JM^.TxtSubChars] := #13;
    Inc(JM^.TxtSubChars);
    End;
  End;


Function JamMsgObj.MsgStartUp: Boolean;
  Var
     SubCtr  : LongInt;
     SubPtr  : ^SubFieldType;
     NumRead : Word;
     DosD    : TDosDate;
     IdxLoc  : LongInt;
     TmpStr  : String;
     TmpAddr : AddrType;
     AktPtr  : PStrList;
     P       : Byte;

  Begin
  While Not (JM^.AttList = Nil) Do
    Begin
    AktPtr := JM^.AttList^.n;
    Dispose(JM^.AttList);
    JM^.AttList := AktPtr;
    End;
  While Not (JM^.ReqList = Nil) Do
    Begin
    AktPtr := JM^.ReqList^.n;
    Dispose(JM^.ReqList);
    JM^.ReqList := AktPtr;
    End;
  Error := 0;
  LastSoft := False;
  JM^.MsgFrom := '';
  JM^.MsgTo := '';
  JM^.MsgSubj := '';
  JM^.TxtSubChars := 0;
  If SeekFound Then
    Begin
    Error := ReReadIdx(IdxLoc);
    If Error = 0 Then
      Begin
      Seek(JM^.HdrFile, JamIdx^[IdxLoc - JM^.IdxStart].HdrLoc);
      Error := IoResult;
      End;
    If Error = 0 Then
      Begin
      BlockRead(JM^.HdrFile, MsgHdr^, SizeOf(MsgHdr^), NumRead);
      Error := IoResult;
      End;
    If Error = 0 Then Begin
       DosD:=UnixDateToDosDate(MsgHdr^.JamHdr.DateWritten);
      JM^.MsgDate := FormattedDosDate(DosD, 'MM-DD-YY');
      JM^.MsgTime := FormattedDosDate(DosD, 'HH:II:SS');
      SubCtr := 1;
      While ((SubCtr <= MsgHdr^.JamHdr.SubFieldLen) and
      (SubCtr < JamSubBufSize)) Do
        Begin
        SubPtr := @MsgHdr^.SubBuf[SubCtr];
        Inc(SubCtr, SubPtr^.DataLen + 8);
        Case(SubPtr^.LoId) Of
          0: Begin {Orig}
             FillChar(TmpAddr, SizeOf(TmpAddr), #0);
             FillChar(JM^.Orig, SizeOf(JM^.Orig), #0);
             TmpStr[0] := Chr(SubPtr^.DataLen and $ff);
             If Byte(TmpStr[0]) > 128 Then
               TmpStr[0] := #128;
             Move(SubPtr^.Data, TmpStr[1], Byte(TmpStr[0]));
             If ParseAddr(TmpStr, TmpAddr, JM^.Orig) Then;
             End;
          1: Begin {Dest}
             FillChar(TmpAddr, SizeOf(TmpAddr), #0);
             FillChar(JM^.Dest, SizeOf(JM^.Dest), #0);
             TmpStr[0] := Chr(SubPtr^.DataLen and $ff);
             If Byte(TmpStr[0]) > 128 Then
               TmpStr[0] := #128;
             Move(SubPtr^.Data, TmpStr[1], Byte(TmpStr[0]));
             If ParseAddr(TmpStr, TmpAddr, JM^.Dest) Then;
             End;
          2: Begin {MsgFrom}
             JM^.MsgFrom[0] := Chr(SubPtr^.DataLen and $ff);
             If Byte(JM^.MsgFrom[0]) > 65 Then
               JM^.MsgFrom[0] := #65;
             Move(SubPtr^.Data, JM^.MsgFrom[1], Byte(JM^.MsgFrom[0]));
             End;
          3: Begin {MsgTo}
             JM^.MsgTo[0] := Chr(SubPtr^.DataLen and $ff);
             If Byte(JM^.MsgTo[0]) > 65 Then
               JM^.MsgTo[0] := #65;
             Move(SubPtr^.Data, JM^.MsgTo[1], Byte(JM^.MsgTo[0]));
             End;
          4: Begin {MsgId}
             TmpStr[0] := Chr(SubPtr^.DataLen and $ff);
             If Byte(TmpStr[0]) > 240 Then
               TmpSTr[0] := #240;
             Move(SubPtr^.Data, TmpStr[1], Byte(TmpStr[0]));
             AddTxtSub(#1'MSGID: ' + TmpStr);
             End;
          5: Begin {Reply}
             TmpStr[0] := Chr(SubPtr^.DataLen and $ff);
             If Byte(TmpStr[0]) > 240 Then
               TmpSTr[0] := #240;
             Move(SubPtr^.Data, TmpStr[1], Byte(TmpStr[0]));
             AddTxtSub(#1'REPLY: ' + TmpStr);
             End;
          6: Begin {MsgSubj}
             JM^.MsgSubj[0] := Chr(SubPtr^.DataLen and $ff);
             If Byte(JM^.MsgSubj[0]) > 100 Then
               JM^.MsgSubj[0] := #100;
             Move(SubPtr^.Data, JM^.MsgSubj[1], Byte(JM^.MsgSubj[0]));
             End;
          7: Begin {PID}
             TmpStr[0] := Chr(SubPtr^.DataLen and $ff);
             If Byte(TmpStr[0]) > 240 Then
               TmpSTr[0] := #240;
             Move(SubPtr^.Data, TmpStr[1], Byte(TmpStr[0]));
             AddTxtSub(#1'PID: ' + TmpStr);
             End;
          8: Begin {VIA}
             TmpStr[0] := Chr(SubPtr^.DataLen and $ff);
             If Byte(TmpStr[0]) > 240 Then
               TmpSTr[0] := #240;
             Move(SubPtr^.Data, TmpStr[1], Byte(TmpStr[0]));
             AddTxtSub(#1'Via ' + TmpStr);
             End;
          9: Begin {File attached}
             If IsFAttach Then
               Begin
               New(AktPtr);
               AktPtr^.n:=JM^.AttList;
               JM^.AttList:=AktPtr;
               With AktPtr^ Do
                 Begin
                 s[0] := Chr(SubPtr^.DataLen and $ff);
                 Move(SubPtr^.Data, s[1], Byte(s[0]));
                 End;
               End;
             End;
          11: Begin {File request}
             If IsFileReq Then
               Begin
               New(AktPtr);
               AktPtr^.n := JM^.ReqList;
               JM^.ReqList:=AktPtr;
               With AktPtr^ Do
                 Begin
                 s[0] := Chr(SubPtr^.DataLen and $ff);
                 Move(SubPtr^.Data, s[1], Byte(s[0]));
                 P := Pos(#0, s);
                 If P > 0 Then
                   Begin
                   Delete(s, P, 1);
                   Insert(' !', s, P);
                   End;
                 End;
               End;
             End;
          2000: Begin {Unknown kludge}
             TmpStr[0] := Chr(SubPtr^.DataLen and $ff);
             If Byte(TmpStr[0]) > 240 Then
               TmpStr[0] := #240;
             Move(SubPtr^.Data, TmpStr[1], Byte(TmpStr[0]));
             If TmpStr<>'' Then
               AddTxtSub(#1 + TmpStr);
             End;
          2001: Begin {SEEN-BY}
             TmpStr[0] := Chr(SubPtr^.DataLen and $ff);
             If Byte(TmpStr[0]) > 240 Then
               TmpStr[0] := #240;
             Move(SubPtr^.Data, TmpStr[1], Byte(TmpStr[0]));
             AddTxtSub(#1'SEEN-BY: ' + TmpStr);
             End;
          2002: Begin {PATH}
             TmpStr[0] := Chr(SubPtr^.DataLen and $ff);
             If Byte(TmpStr[0]) > 240 Then
               TmpSTr[0] := #240;
             Move(SubPtr^.Data, TmpStr[1], Byte(TmpStr[0]));
             AddTxtSub(#1'PATH: ' + TmpStr);
             End;
          2003: Begin {FLAGS}
             TmpStr[0] := Chr(SubPtr^.DataLen and $ff);
             If Byte(TmpStr[0]) > 240 Then
               TmpSTr[0] := #240;
             Move(SubPtr^.Data, TmpStr[1], Byte(TmpStr[0]));
             AddTxtSub(#1'FLAGS ' + TmpStr);
             End;
          End;
        End;
      End;
    End;
    MsgStartup:=Error=0;
  End;


Function  JamMsgObj.MsgTxtStartUp: Boolean;
  Begin
  LastSoft := False;
  JM^.TxtEnd := MsgHdr^.JamHdr.TextOfs + MsgHdr^.JamHdr.TextLen - 1;
  If JM^.TxtSubChars > 0 Then
    JM^.TxtPos := - JM^.TxtSubChars
  Else
    JM^.TxtPos := MsgHdr^.JamHdr.TextOfs;
  MsgTxtSTartup:=True;
  End;


Function JamMsgObj.EOM: Boolean;
  Begin
  EOM := (((JM^.TxtPos < MsgHdr^.JamHdr.TextOfs) Or
    (JM^.TxtPos > JM^.TxtEnd)) And (JM^.TxtPos >= 0));
  End;


Function JamMsgObj.GetFromName: String; {Get from name on current msg}
  Begin
  GetFromName := JM^.MsgFrom;
  End;


Function JamMsgObj.GetToName: TString; {Get to name on current msg}
  Begin
  GetToName := JM^.MsgTo;
  End;


Function JamMsgObj.GetSubj: TString; {Get subject on current msg}
  Begin
  GetSubj := JM^.MsgSubj;
  End;


Function JamMsgObj.GetCost: Word; {Get cost of current msg}
  Begin
  GetCost := MsgHdr^.JamHdr.Cost;
  End;


Function JamMsgObj.GetDateTime: TDateTimeStr; {Get date of current msg}
  Begin
  GetDateTime:=Convert_MMDDYYTime_To_DateTimeStr(JM^.MsgDate, JM^.MsgTime);
  End;

Function JamMsgObj.GetRefer: LongInt; {Get reply to of current msg}
  Begin
  GetRefer := MsgHdr^.JamHdr.ReplyTo;
  End;


Function JamMsgObj.GetSeeAlso: LongInt; {Get see also of current msg}
  Begin
  GetSeeAlso := MsgHdr^.JamHdr.ReplyFirst;
  End;


Function JamMsgObj.GetMsgNum: LongInt; {Get message number}
  Begin
  GetMsgNum := MsgHdr^.JamHdr.MsgNum;
  End;


Function JamMsgObj.GetFromAddr: TFtnAddr; {Get origin address}
  Begin
  GetFromAddr := JM^.Orig;
  End;


Function JamMsgObj.GetToAddr: TFtnAddr; {Get destination address}
  Begin
  GetToAddr := JM^.Dest;
  End;


Function JamMsgObj.IsLocal: Boolean; {Is current msg local}
  Begin
  IsLocal := (MsgHdr^.JamHdr.Attr1 and Jam_Local) <> 0;
  End;


Function JamMsgObj.IsCrash: Boolean; {Is current msg crash}
  Begin
  IsCrash := (MsgHdr^.JamHdr.Attr1 and Jam_Crash) <> 0;
  End;


Function JamMsgObj.IsKillSent: Boolean; {Is current msg kill sent}
  Begin
  IsKillSent := (MsgHdr^.JamHdr.Attr1 and Jam_KillSent) <> 0;
  End;


Function JamMsgObj.IsSent: Boolean; {Is current msg sent}
  Begin
  IsSent := (MsgHdr^.JamHdr.Attr1 and Jam_Sent) <> 0;
  End;


Function JamMsgObj.IsFAttach: Boolean; {Is current msg file attach}
  Begin
  IsFAttach := (MsgHdr^.JamHdr.Attr1 and Jam_FAttch) <> 0;
  End;


Function JamMsgObj.IsReqRct: Boolean; {Is current msg request receipt}
  Begin
  IsReqRct := (MsgHdr^.JamHdr.Attr1 and Jam_RcptReq) <> 0;
  End;


Function JamMsgObj.IsReqAud: Boolean; {Is current msg request audit}
  Begin
  IsReqAud := (MsgHdr^.JamHdr.Attr1 and Jam_ConfmReq) <> 0;
  End;


Function JamMsgObj.IsRetRct: Boolean; {Is current msg a return receipt}
  Begin
  IsRetRct := False;
  End;


Function JamMsgObj.IsFileReq: Boolean; {Is current msg a file request}
  Begin
  IsFileReq := (MsgHdr^.JamHdr.Attr1 and Jam_Freq) <> 0;
  End;


Function JamMsgObj.IsRcvd: Boolean; {Is current msg received}
  Begin
  IsRcvd := (MsgHdr^.JamHdr.Attr1 and Jam_Rcvd) <> 0;
  End;


Function JamMsgObj.IsPriv: Boolean; {Is current msg priviledged/private}
  Begin
  IsPriv := (MsgHdr^.JamHdr.Attr1 and Jam_Priv) <> 0;
  End;


Function JamMsgObj.IsDeleted: Boolean; {Is current msg deleted}
  Begin
  IsDeleted := (MsgHdr^.JamHdr.Attr1 and Jam_Deleted) <> 0;
  End;


Function JamMsgObj.IsEchoed: Boolean; {Is current msg echoed}
  Begin
  IsEchoed := True;
  End;


Function  JamMsgObj.SeekFirst(MsgNum: LongInt;reload:Boolean): Boolean; {Start msg seek}
  Begin
  JM^.CurrMsgNum := MsgNum - 1;
  If JM^.CurrMsgNum < (JM^.BaseHdr.BaseMsgNum - 1) Then
    JM^.CurrMsgNum := JM^.BaseHdr.BaseMsgNum - 1;
  SeekFirst:=SeekNext;
  End;


Function  JamMsgObj.SeekNext: Boolean; {Find next matching msg}
  Var
    IdxLoc: LongInt;

  Begin
  If JM^.CurrMsgNum <= GetHighMsgNum Then
    Inc(JM^.CurrMsgNum);
  Error := ReReadIdx(IdxLoc);
  While (((JamIdx^[IdxLoc - JM^.IdxStart].HdrLoc < 0){ or
  (JamIdx^[IdxLoc - JM^.IdxStart].MsgToCrc = -1)}) And  { auskommentiert wg. eMail-Headern, dort ist der Zielname meistens leer }
  (JM^.CurrMsgNum <= GetHighMsgNum)) Do
    Begin
    Inc(JM^.CurrMsgNum);
    Error := ReReadIdx(IdxLoc);
    End;
  SeekNext:=(Error=0) and SeekFound;
  End;


Function  JamMsgObj.SeekPrior: Boolean;
  Var
    IdxLoc: LongInt;

  Begin
  If JM^.CurrMsgNum >= JM^.BaseHdr.BaseMsgNum Then
    Dec(JM^.CurrMsgNum);
  Error := ReReadIdx(IdxLoc);
  If JM^.CurrMsgNum >= JM^.BaseHdr.BaseMsgNum Then
    Begin
    While (((JamIdx^[IdxLoc - JM^.IdxStart].HdrLoc < 0) or
    (JamIdx^[IdxLoc - JM^.IdxStart].MsgToCrc = -1)) And
    (JM^.CurrMsgNum >= JM^.BaseHdr.BaseMsgNum)) Do
      Begin
      Dec(JM^.CurrMsgNum);
      Error := ReReadIdx(IdxLoc);
      End;
    End;
  SeekPrior:=(Error=0) and SeekFound;
  End;


Function JamMsgObj.SeekFound: Boolean;
  Begin
  SeekFound := ((JM^.CurrMsgNum >= JM^.BaseHdr.BaseMsgNum) and
    (JM^.CurrMsgNum <= GetHighMsgNum));
  End;


Function JamMsgObj.GetMsgLoc: LongInt; {Msg location}
  Begin
  GetMsgLoc := GetMsgNum;
  End;


Procedure JamMsgObj.SetMsgLoc(ML: LongInt); {Msg location}
  Begin
  JM^.CurrMsgNum := ML;
  End;


Function JamMsgObj.YoursFirst(Const Name, Handle: OpenString): Boolean;
  Begin
  JM^.YourName := Copy(Name,1,high(JM^.YourName));
  JM^.YourHdl := Copy(Handle,1,high(JM^.YourHdl));
  JM^.NameCrc := JamStrCrc(Name);
  JM^.HdlCrc := JamStrCrc(Handle);
  JM^.CurrMsgNum := JM^.BaseHdr.BaseMsgNum - 1;
  YoursFirst:=YoursNext;
  End;


Function  JamMsgObj.YoursNext: Boolean;

  Function YoursFound: Boolean;
  Begin
  YoursFound := ((JM^.CurrMsgNum >= JM^.BaseHdr.BaseMsgNum) and
    (JM^.CurrMsgNum <= GetHighMsgNum));
  End;

  Var
    Found: Boolean;
    IdxLoc: LongInt;
    NumRead: Word;
    SubCtr: LongInt;
    SubPtr: ^SubFieldType;

  Begin
  Error := 0;
  Found := False;
  Inc(JM^.CurrMsgNum);
  While ((Not Found) and (JM^.CurrMsgNum <= GetHighMsgNum) And
  (Error = 0)) Do
    Begin
    Error := ReReadIdx(IdxLoc);
    If Error = 0 Then
      Begin                            {Check CRC values}
      If ((JamIdx^[IdxLoc - JM^.IdxStart].MsgToCrc = JM^.NameCrc) or
      (JamIdx^[IdxLoc - JM^.IdxStart].MsgToCrc = JM^.HdlCrc)) Then
        Begin
        Seek(JM^.HdrFile, JamIdx^[IdxLoc - JM^.IdxStart].HdrLoc);
        Error := IoResult;
        If Error = 0 Then              {Read message header}
          Begin
          BlockRead(JM^.HdrFile, MsgHdr^, SizeOf(MsgHdr^), NumRead);
          Error := IoResult;
          End;
        If ((Error = 0) and (Not IsRcvd)) Then
          Begin
          SubCtr := 1;
          While ((SubCtr <= MsgHdr^.JamHdr.SubFieldLen) and
          (SubCtr < JamSubBufSize)) Do
            Begin
            SubPtr := @MsgHdr^.SubBuf[SubCtr];
            Inc(SubCtr, SubPtr^.DataLen + 8);
            Case(SubPtr^.LoId) Of
              3: Begin {MsgTo}
                 JM^.MsgTo[0] := Chr(SubPtr^.DataLen and $ff);
                 If Byte(JM^.MsgTo[0]) > 65 Then
                   JM^.MsgTo[0] := #65;
                 Move(SubPtr^.Data, JM^.MsgTo[1], Byte(JM^.MsgTo[0]));
                 If ((UpString(JM^.MsgTo) = UpString(JM^.YourName)) Or
                 (UpString(JM^.MsgTo) = UpString(JM^.YourHdl))) Then
                   Found := True;
                 End;
              End;
            End;
          End;
        End;
      End;
    If (Not Found) Then
      Inc(JM^.CurrMsgNum);
    End;
    YoursNext:=YoursFound;
  End;


Function JamMsgObj.StartNewMsg: Boolean;
  Begin
  JM^.TxtBufStart := 0;
  JM^.TxtPos := 0;
  FillChar(MsgHdr^, SizeOf(MsgHdr^), #0);
  MsgHdr^.JamHdr.SubFieldLen := 0;
  MsgHdr^.JamHdr.MsgIdCrc := -1;
  MsgHdr^.JamHdr.ReplyCrc := -1;
  MsgHdr^.JamHdr.PwdCrc := -1;
  JM^.MsgTo := '';
  JM^.MsgFrom := '';
  JM^.MsgSubj := '';
  FillChar(JM^.Orig, SizeOf(JM^.Orig), #0);
  FillChar(JM^.Dest, SizeOf(JM^.Dest), #0);
  JM^.MsgDate := ConvertToDateStr(GetDosDate);
  JM^.MsgTime := ConvertToTimeStr(GetDosDate);
  StartNewMsg:=True;
  End;


Function JamMsgObj.MsgBaseExists: Boolean;
  Begin
  MsgBaseExists := (FileExist(JM^.MsgPath + '.JHR'));
  End;


Function JamMsgObj.ReadIdx: Word;
  Begin
  If JM^.IdxStart < 0 Then
    JM^.IdxStart := 0;
  Seek(JM^.IdxFile, JM^.IdxStart);
  BlockRead(JM^.IdxFile, JamIdx^, JamIdxBufSize, JM^.IdxRead);
  ReadIdx := IoResult;
  End;


Function JamMsgObj.WriteIdx: Word;
  Begin
  Seek(JM^.IdxFile, JM^.IdxStart);
  BlockWrite(JM^.IdxFile, JamIdx^, JM^.IdxRead);
  WriteIdx := IoResult;
  End;


Function JamMsgObj.OpenMsgBase: Word;
  Var
    OpenError: Word;

  Begin
  OpenError := 0;
  JM^.LockCount := 0;
  Assign(JM^.HdrFile, JM^.MsgPath + '.JHR');
  Assign(JM^.TxtFile, JM^.MsgPath + '.JDT');
  Assign(JM^.IdxFile, JM^.MsgPath + '.JDX');
  FileMode := fmReadWrite + fmDenyNone;
  Reset(JM^.HdrFile, 1);
  OpenError := IoResult;
  If OpenError = 0 Then
    Begin
    Seek(JM^.HdrFile, 1);
    BlockRead(JM^.HdrFile, JM^.BaseHdr.Signature[2] , SizeOf(JM^.BaseHdr) - 1);
    OpenError := IoResult;
    End;
  If OpenError = 0 Then
    Begin
    FileMode := fmReadWrite + fmDenyNone;
    Reset(JM^.TxtFile, 1);
    OpenError := IoResult;
    End;
  If OpenError = 0 Then
    Begin
    FileMode := fmReadWrite + fmDenyNone;
    Reset(JM^.IdxFile, SizeOf(JamIdxType));
    OpenError := IoResult;
    End;
  JM^.IdxStart := -10;
  JM^.IdxRead := 0;
  JM^.TxtBufStart := - 10;
  JM^.TxtRead := 0;
  OpenMsgBase := OpenError;
  End;


Function JamMsgObj.CloseMsgBase: Word;
  Var
    CloseError: Word;

  Begin
  Close(JM^.HdrFile);
  CloseError := IoResult;
  Close(JM^.TxtFile);
  If CloseError = 0 Then
    CloseError := IoResult;
  Close(JM^.IdxFile);
  If CloseError = 0 Then
    CloseError := IoResult;
  CloseMsgBase := CloseError;
  End;


Function JamMsgObj.CreateMsgBase(MaxMsg: Word; MaxDays: Word): Word;
  Var
    TmpHdr: ^JamHdrType;
    CreateError: Word;
    i: Word;

  Begin
  CreateError := 0;
  i := PosLastChar('\', JM^.MsgPath);
  If i > 0 Then
    Begin
    If Not MakePath(Copy(JM^.MsgPath, 1, i)) Then
      CreateError := 0;
    End;
  New(TmpHdr);
  If TmpHdr = Nil Then
    CreateError := 500
  Else
    Begin;
    FillChar(TmpHdr^, SizeOf(TmpHdr^), #0);
    TmpHdr^.Signature[1] :=  'J';
    TmpHdr^.Signature[2] :=  'A';
    TmpHdr^.Signature[3] :=  'M';
    TmpHdr^.BaseMsgNum := 1;
    TmpHdr^.Created := DosDateToUnixDate(GetDosDate);
    TmpHdr^.PwdCrc := -1;
    CreateError := SaveFile(JM^.MsgPath + '.JHR', TmpHdr^, SizeOf(TmpHdr^));
    Dispose(TmpHdr);
    If CreateError = 0 Then
      CreateError := SaveFile(JM^.MsgPath + '.JLR', CreateError, 0);
    If CreateError = 0 Then
      CreateError := SaveFile(JM^.MsgPath + '.JDT', CreateError, 0);
    If CreateError = 0 Then
      CreateError := SaveFile(JM^.MsgPath + '.JDX', CreateError , 0);
    If IoResult <> 0 Then;
    End;
  CreateMsgBase := CreateError;
  End;


Procedure JamMsgObj.SetMsgBaseType(MT: MsgMailType);
  Begin
  JM^.MailType := MT;
  End;


Function JamMsgObj.ReWriteHdr: Word;
  Var
    IdxLoc: LongInt;

  Begin
  If LockMsgBase Then
    Error := 0
  Else
    Error := 5;
  Error := ReReadIdx(IdxLoc);
  If Error = 0 Then
    Begin
    Seek(JM^.HdrFile, JamIdx^[IdxLoc - JM^.IdxStart].HdrLoc);
    Error := IoResult;
    End;
  If Error = 0 Then
    Begin
    BlockWrite(JM^.HdrFile, MsgHdr^.JamHdr, SizeOf(MsgHdr^.JamHdr));
    Error := IoResult;
    End;
  If UnLockMsgBase Then;
     ReWriteHdr:=Error;
  End;


Function JamMsgObj.KillMsg(MsgNum: LongInt):Integer;

Var
  DelError    : Word;
  IdxLoc      : LongInt;
   LastCurMsg :  LongInt;


Begin
  (* save current message-index *)
  if GetMsgNum <> MsgNum then begin
     LastCurMsg := GetMsgNum;
  end else begin
     LastCurMsg := -1; {user wants to delete current message}
  end;

  if (LastCurMsg<>-1) and (not SeekFirst(MsgNum, false)) then begin
     DelError:=6;
  end else If IsDeleted Then Begin
     DelError:=0;
  end else If not LockMsgBase Then begin
     DelError := 5;
  end else begin
    SetAttr1(Jam_Deleted, True);
    Dec(JM^.BaseHdr.ActiveMsgs);
    DelError := ReReadIdx(IdxLoc);
    If DelError = 0 then begin
      DelError:=ReWriteHdr;
      Inc(JM^.BaseHdr.ModCounter);
      JamIdx^[IdxLoc - JM^.IdxStart].MsgToCrc := -1;
      JamIdx^[IdxLoc - JM^.IdxStart].HdrLoc := -1;
      If WriteIdx=0 Then;
    End;
    If UnLockMsgBase Then;
  End;

  if LastCurMsg<> -1 then begin
    SeekFirst(LastCurMsg, false);
  end;

  KillMsg:=DelError;
End;


Function JamMsgObj.NumberOfMsgs: LongInt;
  Begin
  NumberOfMsgs := JM^.BaseHdr.ActiveMsgs;
  End;


Function JamMsgObj.FindLastRead(Var LastFile: File; UNum: LongInt): LongInt;
  Const
    LastSize = {$IFDEF PPC_BP}100{$ELSE}500{$ENDIF};

  Type LastArray = Array[1..LastSize] of JamLastType;

  Var
    LastBuf: ^LastArray;
    LastError: Word;
    NumRead: Word;
    Found: Boolean;
    i: Word;
    LastStart: LongInt;

  Begin
  FindLastRead := -1;
  Found := False;
  New(LastBuf);
  Seek(LastFile, 0);
  LastError := IoResult;
  While ((Not Eof(LastFile)) and (LastError = 0) And (Not Found)) Do
    Begin
    LastStart := FilePos(LastFile);
    BlockRead(LastFile, LastBuf^, LastSize, NumRead);
    LastError := IoResult;
    For i := 1 to NumRead Do
      Begin
      If LastBuf^[i].UserNum = UNum Then
        Begin
        Found := True;
        FindLastRead := LastStart + i - 1;
        End;
      End;
    End;
  Dispose(LastBuf);
  End;


Function JamMsgObj.GetLastRead_UCRC(UNum, UCRC: LongInt): LongInt;
  Var
    RecNum: LongInt;
    LastFile: File;
    TmpLast: JamLastType;

  Begin
  Assign(LastFile, JM^.MsgPath + '.JLR');
  FileMode := fmReadWrite + fmDenyNone;
  Reset(LastFile, SizeOf(JamLastType));
  Error := IoResult;
  RecNum := FindLastRead(LastFile, UCRC);
  If RecNum >= 0 Then
    Begin
    Seek(LastFile, RecNum);
    If Error = 0 Then
      Begin
      BlockRead(LastFile, TmpLast, 1);
      Error := IoResult;
      GetLastRead_UCRC := TmpLast.HighRead;
      End;
    End
  Else
    GetLastRead_UCRC := 0;
  Close(LastFile);
  Error := IoResult;
  End;


Procedure JamMsgObj.SetLastRead_UCRC(UNum, UCRC: LongInt; LR: LongInt);
  Var
    RecNum: LongInt;
    LastFile: File;
    TmpLast: JamLastType;

  Begin
  Assign(LastFile, JM^.MsgPath + '.JLR');
  FileMode := fmReadWrite + fmDenyNone;
  Reset(LastFile, SizeOf(JamLastType));
  Error := IoResult;
  RecNum := FindLastRead(LastFile, UCRC);
  If RecNum >= 0 Then
    Begin
    Seek(LastFile, RecNum);
    If Error = 0 Then
      Begin
      BlockRead(LastFile, TmpLast, 1);
      Error := IoResult;
      TmpLast.HighRead := LR;
      TmpLast.LastRead := LR;
      If Error = 0 Then
        Begin
        Seek(LastFile, RecNum);
        Error := IoResult;
        End;
      If Error = 0 Then
        Begin
        BlockWrite(LastFile, TmpLast, 1);
        Error := IoResult;
        End;
      End;
    End
  Else
    Begin
    TmpLast.UserNum := UNum;
    TmpLast.HighRead := Lr;
    TmpLast.NameCrc := UCRC;
    TmpLast.LastRead := Lr;
    Seek(LastFile, FileSize(LastFile));
    Error := IoResult;
    If Error = 0 Then
      Begin
      BlockWrite(LastFile, TmpLast, 1);
      Error := IoResult;
      End;
    End;
  Close(LastFile);
  Error := IoResult;
  End;


Function JamMsgObj.GetTxtPos: LongInt;
  Begin
  GetTxtPos := JM^.TxtPos;
  End;


Procedure JamMsgObj.SetTxtPos(TP: LongInt);
  Begin
  JM^.TxtPos := TP;
  End;


Function JamMsgObj.LockMsgBase: Boolean;
  Var
    LockError: Word;

  Begin
  LockError := 0;
  If JM^.LockCount = 0 Then
    Begin
    If LockError = 0 Then
      Begin
      LockError := shLock(JM^.HdrFile, 0, 1);
      End;
    If LockError = 0 Then
      Begin
      Seek(JM^.HdrFile, 0);
      LockError := IoResult;
      End;
    If LockError = 0 Then
      Begin
      BlockRead(JM^.HdrFile, JM^.BaseHdr , SizeOf(JM^.BaseHdr));
      LockError := IoResult;
      End;
    End;
  Inc(JM^.LockCount);
  LockMsgBase := (LockError = 0);
  End;


Function JamMsgObj.UnLockMsgBase: Boolean;
  Var
    LockError: Word;

  Begin
  LockError := 0;
  If JM^.LockCount > 0 Then
    Dec(JM^.LockCount);
  If JM^.LockCount = 0 Then
    Begin
    If LockError = 0 Then
      Begin
      LockError := UnLockFile(JM^.HdrFile, 0, 1);
      End;
    If LockError = 0 Then
      Begin
      Seek(JM^.HdrFile, 0);
      LockError := IoResult;
      End;
    If LockError = 0 Then
      Begin
      BlockWrite(JM^.HdrFile, JM^.BaseHdr, SizeOf(JM^.BaseHdr));
      LockError := IoResult;
      End;
    End;
  UnLockMsgBase := (LockError = 0);
  End;

{SetSeeAlso/GetSeeAlso provided by 2:201/623@FidoNet Jonas@iis.bbs.bad.se}


Procedure JamMsgObj.SetNextSeeAlso(SAlso: LongInt);
  Begin
  MsgHdr^.JamHdr.ReplyNext := SAlso;
  End;

Function JamMsgObj.GetNextSeeAlso: LongInt; {Get next see also of current msg}
  Begin
  GetNextSeeAlso := MsgHdr^.JamHdr.ReplyNext;
  End;


Function JamMsgObj.ReReadIdx(Var IdxLoc : LongInt) : Word;
  Begin
  ReReadIdx := 0;
  IdxLoc := JM^.CurrMsgNum - JM^.BaseHdr.BaseMsgNum;
  If ((IdxLoc < JM^.IdxStart) OR
    (IdxLoc >= (JM^.IdxStart+JM^.IdxRead))) Then
    Begin
    JM^.IdxStart := IdxLoc - 30;
    If JM^.IdxStart < 0 Then JM^.IdxStart := 0;
    ReReadIdx := ReadIdx;
    End;
  End;


Procedure JamMsgObj.SetFwd(St: Boolean);
  Begin
  SetAttr1(Jam_InTransit, St);
  End;

Function JamMsgObj.IsFwd: Boolean;
  Begin
  IsFwd := (MsgHdr^.JamHdr.Attr1 and Jam_InTransit) <> 0;
  End;


Function JamMsgObj.GetFAttach: String;
  Var
    AktPtr      : PStrList;

  Begin
  If JM^.AttList <> nil Then
    Begin
      GetFAttach := JM^.AttList^.s;
      AktPtr := JM^.AttList^.n;
      Dispose(JM^.AttList);
      JM^.AttList := AktPtr;
    End
  Else
    GetFAttach := '';
  End;


Function JamMsgObj.GetFRequest: String;
  Var
    AktPtr      : PStrList;

  Begin
  If JM^.ReqList <> nil Then
    Begin
      GetFRequest := JM^.ReqList^.s;
      AktPtr := JM^.ReqList^.n;
      Dispose(JM^.ReqList);
      JM^.ReqList := AktPtr;
    End
  Else
    GetFRequest := '';
  End;


Procedure JamMsgObj.AddFAttach(const s: String);
  Begin
  AddSubField(9, s);
  End;


Procedure JamMsgObj.AddFRequest(const s: String);
  Var
    i           : byte;

  Begin
    i := Pos('!',s);
    If i>0 Then
      AddSubField(11, TrimStr(Copy(s, 1, pred(i))) + #0 +
                      TrimStr(Copy(s, succ(i), 255)))
    Else
      AddSubField(11, TrimStr(s));
  End;

(* only stubs *)
Function JamMsgObj.GetLastRead(UNum: LongInt):Longint;
begin
   GetLastRead:=-1;
end;

Procedure JamMsgObj.SetLastRead(UNum: LongInt; LR: LongInt);
begin
end;

Function JamMsgObj.IsHold: Boolean;
  Begin
     IsHold := false;
  End;

Procedure JamMsgObj.SetHold(St: Boolean);
  Begin
  End;

Procedure JamMsgObj.SetEcho(ES: Boolean);
  Begin
     {original routines always return TRUE @ isEcho, therefore here is no code needed}
  End;

Procedure JamMsgObj.SetRetRct(St: Boolean);
  Begin
  End;

End.

