{$IFDEF MSDOS}
{$F+,O+}
{$ENDIF}

Unit MKMsgJam;       {JAM Msg Object Unit}

Interface

Uses
  MKGlobT,
  MKMsgAbs,
{$IFNDEF WIN32}
  DOS,
{$ELSE}
  OpCrt,
  SysUtils,
{$ENDIF}
  tMisc,
  MKFile,
  tGlob,
  Crc32,
  MKDos;

Const
{$IFDEF MSDOS}
  JamIdxBufSize = 1500;
  JamSubBufSize = 2000;
  JamTxtBufSize = 2000;
  TxtSubBufSize = 1000;
{$ELSE}
  JamIdxBufSize = 16000;
  JamSubBufSize = 8000;
  JamTxtBufSize = 32000;
  TxtSubBufSize = 4000;
{$ENDIF}
  UName: String = '';

Type
  JamHdrType = Record
    Signature            : Array [1..4] Of Char;
    Created, ModCounter,
    ActiveMsgs, PwdCRC,
    BaseMsgNum           : LongInt;
    Extra                : Array [1..1000] Of Char;
  End;

  JamMsgHdrType = Record
    Signature                  : Array [1..4] Of Char;
    Rev, Resvd                 : Word;
    SubFieldLen, TimesRead,
    MsgIdCrc, ReplyCrc,
    ReplyTo, ReplyFirst,
    ReplyNext, DateWritten,
    DateRcvd, DateArrived,
    MsgNum, Attr1, Attr2,
    TextOfs, TextLen,
    PwdCrc, Cost               : LongInt;
  End;

  JamIdxType = Record
    MsgToCrc, HdrLoc : LongInt;
  End;

  JamLastType = Record
    NameCrc, UserNum,
    LastRead, HighRead  : LongInt;
  End;

  JamIdxArrayType = Array [0..JamIdxBufSize] Of JamIdxType;
  JamSubBuffer = Array [1..JamSubBufSize] Of Char;

  JamTxtBufType = Array [0..JamTxtBufSize] Of Char;
  HdrType = Record
    JamHdr: JamMsgHdrType;
    SubBuf: JamSubBuffer;
  End;

  JamMsgType = Record
    HdrFile, TxtFile,
    IdxFile             : shFile;
    MsgPath             : String [128];
    BaseHdr             : JamHdrType;
    Dest, Orig          : AddrType;
    MsgFrom, MsgTo      : String [65];
    MsgSubj             : String [100];
    MsgDate             : String [8];
    MsgTime             : String [5];
    CurrMsgNum          : LongInt;
    YourName, YourHdl   : String [35];
    NameCrc, HdlCrc,
    TxtPos, TxtEnd,
    TxtBufStart         : LongInt;
    TxtRead, IdxRead    : SysInt;
    MailType            : MsgMailType;
    BufFile             : shFile;
    LockCount, IdxStart : LongInt;
    TxtSubBuf           : Array [0..TxtSubBufSize] Of Char; {temp storage for text on subfields}
    TxtSubChars         : Integer;
  End;

  SubFieldType = Record
    LoId, HiId : Word;
    DataLen    : LongInt;
    Data       : Array [1..1000] Of Char;
  End;

  JamMsgObj = Object (AbsMsgObj)
    JM                    : ^JamMsgType;
    MsgHdr                : ^HdrType;
    JamIdx                : ^JamIdxArrayType;
    TxtBuf                : ^JamTxtBufType;
    Error                 : Word;
    SeekOver,
    NeedReCount,
    NeedGetTotal,
    IncRelative,
    NeedReSeek            : Boolean;
    CurRelative,
    TotalMsgsNumber,
    StartMsgNumber,
    EndMsgNumber          : LongInt;

    Constructor Init; {Initialize}
    Destructor Done; Virtual; {Done}
    Procedure SetMsgPath (St: String); Virtual; {Set netmail path}
    Function  GetHighMsgNum: LongInt; Virtual; {Get highest netmail msg number in area}
    Function  LockMsgBase: Boolean; Virtual; {Lock the message base}
    Function  UnLockMsgBase: Boolean; Virtual; {Unlock the message base}
    Procedure SetDest (Var Addr: AddrType); Virtual; {Set Zone/Net/Node/Point for Dest}
    Procedure SetOrig (Var Addr: AddrType); Virtual; {Set Zone/Net/Node/Point for Orig}
    Procedure SetFrom (Name: String); Virtual; {Set message from}
    Procedure SetTo (Name: String); Virtual; {Set message to}
    Procedure SetSubj (Str: String); Virtual; {Set message subject}
    Procedure SetCost (SCost: Word); Virtual; {Set message cost}
    Procedure SetRefer (SRefer: LongInt); Virtual; {Set message reference}
    Procedure SetSeeAlso (SAlso: LongInt); Virtual; {Set message see also}
    Function  GetNextSeeAlso: LongInt; Virtual;
    Procedure SetNextSeeAlso (SAlso: LongInt); Virtual;
    Procedure SetDate (SDate: String); Virtual; {Set message date}
    Procedure SetTime (STime: String); Virtual; {Set message time}
    Procedure SetLocal (LS: Boolean); Virtual; {Set local status}
    Procedure SetRcvd (RS: Boolean); Virtual; {Set received status}
    Procedure SetPriv (PS: Boolean); Virtual; {Set priveledge vs public status}
    Procedure SetCrash (SS: Boolean); Virtual; {Set crash netmail status}
    Procedure SetKillSent (SS: Boolean); Virtual; {Set kill/sent netmail status}
    Procedure SetSent (SS: Boolean); Virtual; {Set sent netmail status}
    Procedure SetFAttach (SS: Boolean); Virtual; {Set file attach status}
    Procedure SetReqRct (SS: Boolean); Virtual; {Set request receipt status}
    Procedure SetReqAud (SS: Boolean); Virtual; {Set request audit status}
    Procedure SetRetRct (SS: Boolean); Virtual; {Set return receipt status}
    Procedure SetFileReq (SS: Boolean); Virtual; {Set file request status}
    Procedure SetHold (SS: Boolean); Virtual; {Set file hold status}
    Procedure SetDirect (SS: Boolean); Virtual; {Set file direct status}
    Procedure DoString (Str: String); Virtual; {Add string to message text}
    Procedure DoChar (CH: Char); Virtual; {Add character to message text}
    Procedure DoStringLn (Str: String); Virtual; {Add string and newline to msg text}
    Procedure DoKludgeLn (Str: String); Virtual; {Add ^AKludge line to msg}
    Function  WriteMsg: Word; Virtual;
    Function  ReWriteMsg: Word; Virtual;
    Function  GetChar: Char; Virtual;
    Procedure AddTxtSub (St: String);
    Procedure MsgStartUp; Virtual; {set up msg for reading}
    Function  EOM: Boolean; Virtual; {No more msg text}
    Function  GetString (MaxLen: Word): String; Virtual; {Get wordwrapped string}
    Function  WasWrap: Boolean; Virtual; {Last line was soft wrapped no CR}
    Procedure SeekFirst (MsgNum: LongInt); Virtual; {Seek msg number}
    Procedure SeekNext; Virtual; {Find next matching msg}
    Procedure SeekPrior; Virtual; {Seek prior matching msg}
    Function  GetFrom: String; Virtual; {Get from name on current msg}
    Function  GetTo: String; Virtual; {Get to name on current msg}
    Function  GetSubj: String; Virtual; {Get subject on current msg}
    Function  GetCost: Word; Virtual; {Get cost of current msg}
    Function  GetDate: String; Virtual; {Get date of current msg}
    Function  GetTime: String; Virtual; {Get time of current msg}
    Function  GetRefer: LongInt; Virtual; {Get reply to of current msg}
    Function  GetSeeAlso: LongInt; Virtual; {Get see also of current msg}
    Function  GetMsgNum: LongInt; Virtual; {Get message number}
    Procedure GetOrig (Var Addr: AddrType); Virtual; {Get origin address}
    Procedure GetDest (Var Addr: AddrType); Virtual; {Get destination address}
    Function  IsLocal: Boolean; Virtual; {Is current msg local}
    Function  IsCrash: Boolean; Virtual; {Is current msg crash}
    Function  IsKillSent: Boolean; Virtual; {Is current msg kill sent}
    Function  IsSent: Boolean; Virtual; {Is current msg sent}
    Function  IsFAttach: Boolean; Virtual; {Is current msg file attach}
    Function  IsReqRct: Boolean; Virtual; {Is current msg request receipt}
    Function  IsReqAud: Boolean; Virtual; {Is current msg request audit}
    Function  IsRetRct: Boolean; Virtual; {Is current msg a return receipt}
    Function  IsFileReq: Boolean; Virtual; {Is current msg a file request}
    Function  IsRcvd: Boolean; Virtual; {Is current msg received}
    Function  IsPriv: Boolean; Virtual; {Is current msg priviledged/private}
    Function  IsDeleted: Boolean; Virtual; {Is current msg deleted}
    Function  IsEchoed: Boolean; Virtual; {Msg should be echoed}
    Function  IsDirect: Boolean; Virtual; {Is current msg local}
    Function  IsHold: Boolean; Virtual; {Is current msg local}
    Function  GetMsgLoc: LongInt; Virtual; {Msg location}
    Procedure SetMsgLoc (ML: LongInt); Virtual; {Msg location}
    Procedure YoursFirst (Name: String; Handle: String); Virtual; {Seek your mail}
    Procedure YoursNext; Virtual; {Seek next your mail}
    Function  YoursFound: Boolean; Virtual; {Message found}
    Procedure StartNewMsg; Virtual;
    Function  OpenMsgBase: Word; Virtual;
    Function  CloseMsgBase: Word; Virtual;
    Function  MsgBaseExists: Boolean; Virtual; {Does msg base exist}
    Function  CreateMsgBase (MaxMsg: Word; MaxDays: Word): Word; Virtual;
    Function  SeekFound: Boolean; Virtual;
    Procedure SetMailType (MT: MsgMailType); Virtual; {Set message base type}
    Function  GetSubArea: Word; Virtual; {Get sub area number}
    Procedure ReWriteHdr; Virtual; {Rewrite msg header after changes}
    Procedure DeleteMsg (CarePos: Boolean); Virtual; {Delete current message}
    Function  NumberOfMsgs: LongInt; Virtual; {Number of messages}
    Function  GetLastRead (UNum: LongInt): LongInt; Virtual; {Get last read for user num}
    Procedure SetLastRead (UNum: LongInt; LR: LongInt); Virtual; {Set last read}
    Procedure MsgTxtStartUp; Virtual; {Do message text start up tasks}
    Function  GetTxtPos: LongInt; Virtual; {Get indicator of msg text position}
    Procedure SetTxtPos (TP: LongInt); Virtual; {Set text position}
    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: shFile; UNum: LongInt): LongInt;
    Function  ReReadIdx (Var IdxLoc : LongInt) : Word;
    Function  GetMsgNumRelative: LongInt; Virtual;
    Procedure GetTotalMsgs; Virtual;
    Function  GetLowMsgNum: LongInt; Virtual;
    Procedure SeekCurMsg;
    Function  GetHighMsgNumDir: LongInt; Virtual;
  End;

  JamMsgPtr = ^JamMsgObj;

Function JamStrCrc (St: String): LongInt;

Implementation

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;

Var
  TmpFileName   : PathStr;

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
    If JM <> Nil Then Dispose (JM);
    If JamIdx <> Nil Then Dispose (JamIdx);
    If MsgHdr <> Nil Then Dispose (MsgHdr);
    If TxtBuf <> Nil Then Dispose (TxtBuf);
    Fail;
    Exit;
  End;

  FillChar (JM^, SizeOf (JM^), #0);
  FillChar (MsgHdr^, SizeOf (MsgHdr^), #0);

  {!!!}
  FillChar (JamIdx^, SizeOf (JamIdx^), #0);

  { FillChar (TxtBuf^, SizeOf (TxtBuf^), #0); }
  JM^. IdxStart := -30;
  Error := 0;
  StartMsgNumber := 0;
  TotalMsgsNumber := 0;
  EndMsgNumber := Abs (MaxLongInt);
End;

Destructor JamMsgObj. Done;
Begin
  shClose (JM^. HdrFile);
  shClose (JM^. TxtFile);
  shClose (JM^. IdxFile);

  If JM <> Nil Then Dispose (JM);
  If JamIdx <> Nil Then Dispose (JamIdx);
  If MsgHdr <> Nil Then Dispose (MsgHdr);
  If TxtBuf <> Nil Then Dispose (TxtBuf);
End;

Function JamStrCrc (St: String): LongInt;
Var
  i     : Word;
  Crc   : LongInt;

Begin
  Crc := - 1;
  For i := 1 To Length (St) Do
    Crc := UpdateCrc32 (Ord (LoCase (St [i] )), Crc);
  JamStrCrc := Crc;
End;

Procedure JamMsgObj. SetMsgPath (St: String);
Begin
  JM^. MsgPath := Copy (St, 1, 124);
  NeedReCount := True;
  IncRelative := False;
End;

Function JamMsgObj. GetHighMsgNum: LongInt;
Begin
  GetHighMsgNum := EndMsgNumber;
End;

Function JamMsgObj. GetHighMsgNumDir: LongInt;
Begin
  GetHighMsgNumDir := JM^. BaseHdr. BaseMsgNum + shFileSize (JM^. IdxFile) - 1;
End;

Procedure JamMsgObj. SetDest (Var Addr: AddrType);
Begin
  JM^. Dest := Addr;
End;

Procedure JamMsgObj. SetOrig (Var Addr: AddrType);
Begin
  JM^. Orig := Addr;
End;

Procedure JamMsgObj. SetFrom (Name: String);
Begin
  JM^. MsgFrom := Name;
End;

Procedure JamMsgObj. SetTo (Name: String);
Begin
  JM^. MsgTo := Name;
End;

Procedure JamMsgObj. SetSubj (Str: String);
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. SetDate (SDate: String);
Begin
  JM^. MsgDate := SDate;
End;

Procedure JamMsgObj. SetTime (STime: String);
Begin
  JM^. MsgTime := STime;
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 (SS: Boolean);
Begin
  SetAttr1 (Jam_Crash, SS);
End;

Procedure JamMsgObj. SetKillSent (SS: Boolean);
Begin
  SetAttr1 (Jam_KillSent, SS);
End;

Procedure JamMsgObj. SetSent (SS: Boolean);
Begin
  SetAttr1 (Jam_Sent, SS);
End;

Procedure JamMsgObj. SetFAttach (SS: Boolean);
Begin
  SetAttr1 (Jam_FAttch, SS);
End;

Procedure JamMsgObj. SetReqRct (SS: Boolean);
Begin
  SetAttr1 (Jam_RcptReq, SS);
End;

Procedure JamMsgObj. SetReqAud (SS: Boolean);
Begin
  SetAttr1 (Jam_ConfmReq, SS);
End;

Procedure JamMsgObj. SetRetRct (SS: Boolean);
Begin
End;

Procedure JamMsgObj. SetFileReq (SS: Boolean);
Begin
  SetAttr1 (Jam_Freq, SS);
End;

Procedure JamMsgObj. SetDirect (SS: Boolean);
Begin
  SetAttr1 (Jam_Direct, SS);
End;

Procedure JamMsgObj. SetHold (SS: Boolean);
Begin
  SetAttr1 (Jam_Hold, SS);
End;

Procedure JamMsgObj. DoString (Str: String);
Var
  i     : Word;

Begin
  i := 1;
  If Str [1] = #1 Then DoKludgeLn (Str) Else
  While i <= Length (Str) Do
  Begin
    DoChar (Str [i]);
    Inc (i);
  End;
End;

Procedure JamMsgObj. DoChar (CH: Char);
Var
  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, TmpFileName);
      TmpFileName := GetTempName (TmpFileName);
      shAssign (JM^. BufFile, TmpFileName);
      FileMode := fmReadWrite + fmDenyNone;
      shReWrite (JM^. BufFile, 1);
    End;

    NumWrite := JM^. TxtPos - JM^. TxtBufStart;
    shWrite (JM^. BufFile, TxtBuf^, NumWrite);
    Error := shIOResult;
    JM^. TxtBufStart := shFileSize (JM^. BufFile);
  End;

  TxtBuf^ [JM^. TxtPos - JM^. TxtBufStart] := CH;
  Inc (JM^. TxtPos);
End;

Procedure JamMsgObj. DoStringLn (Str: String);
Begin
  DoString (Str);
  If Str [1] <> #1 Then DoChar (#13);
End;

Procedure JamMsgObj. DoKludgeLn (Str: String);
Var
  TmpStr        : String;

Begin
  If Str [1] = #1 Then Str := Copy (Str, 2, 255);
  If Copy (Str, 1, 3) = 'PID' Then
  Begin
    TmpStr := Copy (Trim (Copy (Str, 5, 255)), 1, 40);
    AddSubField (7, TmpStr);
  End Else
  If Copy (Str, 1, 5) = 'MSGID' Then
  Begin
    TmpStr := Copy (Str, 7, 255);
    TmpStr := Copy (Trim (Copy (Str, 7, 255)), 1, 100);
    AddSubField (4, TmpStr);
    MsgHdr^. JamHdr. MsgIdCrc := JamStrCrc (TmpStr);
  End Else
  If Copy (Str, 1, 4) = 'INTL' Then  {ignore}
  Begin
  End Else
  If Copy (Str, 1, 4) = 'TOPT' Then  {ignore}
  Begin
  End Else
  If Copy (Str, 1, 4) = 'FMPT' Then  {ignore}
  Begin
  End Else
  If Copy (Str, 1, 5) = 'REPLY' Then
  Begin
    TmpStr := Copy (Trim (Copy (Str, 8, 255)), 1, 100);
    AddSubField (5, TmpStr);
    MsgHdr^. JamHdr. ReplyCrc := JamStrCrc (TmpStr);
  End Else
  If Copy (Str, 1, 4) = 'PATH' Then
  Begin
    TmpStr := Trim (Copy (Str, 6, 255));
    AddSubField (2002, TmpStr);
  End Else
    AddSubField (2000, Trim (Str));
End;

Procedure JamMsgObj. AddSubField (id: Word; Data: String);
Type
  SubFieldType = Record
    LoId, HiId : Word;
    DataLen    : LongInt;
    Data       : Array [1..256] Of Char;
  End;

Var
  SubField: ^SubFieldType;

Begin
  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
  DT            : DateTime;
  WriteError    : Word;
  i             : SysInt;
  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
      mmtNormal        : SetAttr1 (Jam_TypeLocal, True);
      mmtEchoMail      : SetAttr1 (Jam_TypeEcho, True);
      mmtNetMail       : SetAttr1 (Jam_TypeNet, True);
    End;

    MsgHdr^. JamHdr. Rev := 1;
    MsgHdr^. JamHdr. DateArrived := ToUnixDate (GetDosDate); {Get date processed}
    DT. Year := Str2Long (Copy (JM^. MsgDate, 7, 2)); {Convert date written}
    DT. Month := Str2Long (Copy (JM^. MsgDate, 1, 2));
    DT. Day := Str2Long (Copy (JM^. MsgDate, 4, 2));
    If DT. Year < 80 Then Inc (DT. Year, 2000) Else Inc (DT. Year, 1900);
    DT. Sec := 0;
    DT. Hour := Str2Long (Copy (JM^. MsgTime, 1, 2));
    DT. Min := Str2Long (Copy (JM^. MsgTime, 4, 2));
    MsgHdr^. JamHdr. DateWritten := DateTime2UnixDate (DT);
  End;

  If WriteError = 0 Then {Lock message base for update}
  If Not LockMsgBase Then WriteError := 5;

  If WriteError = 0 Then
  Begin                              {Handle message text}
    MsgHdr^. JamHdr. TextOfs := shFileSize (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;
      shWrite (JM^. BufFile, TxtBuf^, i); {write buffer to file}
      WriteError := shIOResult;

      If WriteError = 0 Then           {seek start of buffer file}
      Begin
        shSeekFile (JM^. BufFile, 0);
        WriteError := shIOResult;
      End;

      If WriteError = 0 Then           {seek end of text file}
      Begin
        shSeekFile (JM^. TxtFile, shFileSize (JM^. TxtFile));
        WriteError := shIOResult;
      End;

      While ((Not shEoF (JM^. BufFile)) And (WriteError = 0)) Do
      Begin                          {copy buffer file to text file}
        shRead (JM^. BufFile, TxtBuf^, SizeOf (TxtBuf^), i);
        WriteError := shIOResult;
        If WriteError = 0 Then
        Begin
          JM^. TxtBufStart := shFilePos (JM^. TxtFile);
          JM^. TxtRead := i;
          shWrite (JM^. TxtFile, TxtBuf^, i);
          Error := shIOResult;
        End;
      End;

      shClose (JM^. BufFile); Error := shIOResult;
      tDeleteFile (TmpFileName);
      Error := shIOResult;
    End Else
    Begin                            {Write text using TxtBuf only}
      shSeekFile (JM^. Txtfile, shFileSize (JM^. TxtFile));
      WriteError := shIOResult;
      If WriteError = 0 Then
      Begin
        shWrite (JM^. TxtFile, TxtBuf^, JM^. TxtPos);
        WriteError := shIOResult;
        JM^. TxtRead := JM^. TxtPos;
      End;
    End;

    If WriteError = 0 Then             {Add index record}
    Begin
      TmpIdx. HdrLoc := shFileSize (JM^. HdrFile);
      TmpIdx. MsgToCrc := JamStrCrc (JM^. MsgTo);
      shSeekFile (JM^. IdxFile, shFileSize (JM^. IdxFile));
      WriteError := shIOResult;
    End;

    If WriteError = 0 Then             {write index record}
    Begin
      shWrite (JM^. IdxFile, TmpIdx, 1);
      WriteError := shIOResult;
    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
      If IsFileReq Then AddSubField (11, JM^. MsgSubj) Else 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));

      shSeekFile (JM^. HdrFile, shFileSize (JM^. HdrFile)); {shSeekFile to end of .jhr file}
      WriteError := shIOResult;
    End;

    If WriteError = 0 Then
    Begin                            {write msg header}
      shWrite (JM^. HdrFile, MsgHdr^, SizeOf (MsgHdr^. JamHdr) +
      MsgHdr^. JamHdr. SubFieldLen);
      WriteError := shIOResult;
    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;

  WriteError := ReadIdx;
  WriteMsg := WriteError;              {return result of writing msg}

  NeedReSeek := True;
  EndMsgNumber := JM^. BaseHdr. BaseMsgNum + shFileSize (JM^. IdxFile) - 1;
  If JM^. IdxRead < JamIdxBufSize Then JamIdx^ [JM^. IdxRead] := TmpIdx;
  Inc (TotalMsgsNumber);

  If StartMsgNumber = 0 Then
  Begin
    StartMsgNumber := MsgHdr^. JamHdr. MsgNum;
    EndMsgNumber := Abs (MaxLongInt);
    TotalMsgsNumber := 0;
    GetTotalMsgs;
  End;
End;

Function JamMsgObj. ReWriteMsg: Word;
Var
  WriteError  : Word;
  i           : SysInt;
  IdxLoc, L   : LongInt;

Begin
  WriteError := 0;

  If LastSoft Then
  Begin
    DoChar (#13);
    DoChar (#10);
  End;

  If WriteError = 0 Then {Lock message base for update}
  If Not LockMsgBase Then WriteError := 5;

  If WriteError = 0 Then
  Begin                              {Handle message text}
    Case JM^. MailType Of
      mmtNormal        : SetAttr1 (Jam_TypeLocal, True);
      mmtEchoMail      : SetAttr1 (Jam_TypeEcho, True);
      mmtNetMail       : SetAttr1 (Jam_TypeNet, True);
    End;

    If JM^. TxtBufStart > 0 Then
    Begin                            {Write text using buffer file}
      i := JM^. TxtPos - JM^. TxtBufStart;
      shWrite (JM^. BufFile, TxtBuf^, i); {write buffer to file}
      WriteError := shIOResult;

      If WriteError = 0 Then           {shSeekFile start of buffer file}
      Begin
        shSeekFile (JM^. BufFile, 0);
        WriteError := shIOResult;
      End;

      If WriteError = 0 Then           {shSeekFile end of text file}
      Begin
        shSeekFile (JM^. TxtFile, shFileSize (JM^. TxtFile));
        WriteError := shIOResult;
      End;

      While ((Not shEoF (JM^. BufFile)) And (WriteError = 0)) Do
      Begin                          {copy buffer file to text file}
        shRead (JM^. BufFile, TxtBuf^, SizeOf (TxtBuf^), i);
        WriteError := shIOResult;
        If WriteError = 0 Then
        Begin
          JM^. TxtBufStart := shFilePos (JM^. TxtFile);
          JM^. TxtRead := i;
          shWrite (JM^. TxtFile, TxtBuf^, i);
          Error := shIOResult;
        End;
      End;

      shClose (JM^. BufFile); Error := shIOResult;
      tDeleteFile (TmpFileName); Error := shIOResult;
    End Else
    Begin                            {Write text using TxtBuf only}
      shSeekFile (JM^. Txtfile, shFileSize (JM^. TxtFile));
      WriteError := shIOResult;
      If WriteError = 0 Then
      Begin
        shWrite (JM^. TxtFile, TxtBuf^, JM^. TxtPos);
        WriteError := shIOResult;
        JM^. TxtRead := JM^. TxtPos;
      End;
    End;

    If WriteError = 0 Then
    Begin                            {write msg header}
      ReReadIdx (IdxLoc);
      shSeekFile (JM^. HdrFile, JamIdx^ [IdxLoc - JM^. IdxStart]. HdrLoc + 60);
      MsgHdr^. JamHdr. TextOfs := shFileSize (JM^. TxtFile)-JM^. TxtPos;
      MsgHdr^. Jamhdr. TextLen := JM^. TxtPos;

      L := MsgHdr^. JamHdr. TextOfs; shWrite (JM^. HdrFile, L, 4);
      L := MsgHdr^. JamHdr. TextLen; shWrite (JM^. HdrFile, L, 4);

      shIOResult;
    End;

    If UnLockMsgBase Then;             {unlock msg base}
  End;

  WriteError := ReadIdx;
  ReWriteMsg := 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;
      shSeekFile (JM^. TxtFile, JM^. TxtBufStart);
      Error := shIOResult;
      If Error = 0 Then
      Begin
        shRead (JM^. TxtFile, TxtBuf^, SizeOf (TxtBuf^), JM^. TxtRead);
        Error := shIOResult;
      End;
    End;
    GetChar := TxtBuf^ [JM^. TxtPos - JM^. TxtBufStart];
    Inc (JM^. TxtPos);
  End;
End;

Procedure JamMsgObj. AddTxtSub (St: String);
Var
  i     : Word;

Begin
  For i := 1 To Length (St) Do
  If JM^. TxtSubChars <= TxtSubBufSize Then
  Begin
    JM^. TxtSubBuf [JM^. TxtSubChars] := St [i];
    Inc (JM^. TxtSubChars);
  End;

  If JM^. TxtSubChars <= TxtSubBufSize Then
  Begin
    JM^. TxtSubBuf [JM^. TxtSubChars] := #13;
    Inc (JM^. TxtSubChars);
  End;
End;

Procedure JamMsgObj. MsgStartUp;
Var
  SubCtr, IdxLoc        : LongInt;
  SubPtr                : ^SubFieldType;
  NumRead               : SysInt;
  DT                    : DateTime;
  TmpStr                : String;
  TmpAddr               : AddrType;
  AddrMsgID             : Boolean;

Begin
  Error := 0;
  LastSoft := False;
  JM^. MsgFrom := '';
  JM^. MsgTo := '';
  JM^. MsgSubj := '';
  JM^. TxtSubChars := 0;
  AddrMsgID := False;
  FillChar (JM^. Orig, SizeOf (JM^. Orig), #0);

  If SeekFound Then
  Begin
    Error := ReReadIdx (IdxLoc);

    If Error = 0 Then
    Begin
      shSeekFile (JM^. HdrFile, JamIdx^ [IdxLoc - JM^. IdxStart]. HdrLoc);
      Error := shIOResult;
    End;

    If Error = 0 Then
    Begin
      shRead (JM^. HdrFile, MsgHdr^, SizeOf (MsgHdr^), NumRead);
      Error := shIOResult;
    End;

    If Error = 0 Then
    Begin
      UnixDate2DateTime (MsgHdr^. JamHdr. DateWritten, DT);
      JM^. MsgDate := FormattedDate (Dt, 'MM-DD-YY');
      JM^. MsgTime := FormattedDate (Dt, 'HH:II');
      SubCtr := 1;
      While ((SubCtr <= MsgHdr^. JamHdr. SubFieldLen) And
             (SubCtr < JamSubBufSize)) Do
      Begin
        SubPtr := @MsgHdr^. SubBuf [SubCtr];
        If SubPtr^. DataLen < 0 Then SubPtr^. DataLen := 0;
        Inc (SubCtr, SubPtr^. DataLen + 8);

        Case (SubPtr^. LoId) Of
         0 : Begin {Orig}
               FillChar (TmpAddr, SizeOf (TmpAddr), #0);
               SetLength (TmpStr, SubPtr^. DataLen And $ff);
               If Length (TmpStr) > 128 Then SetLength (TmpStr, 128);
               Move (SubPtr^. Data, TmpStr [1], Length (TmpStr));
               If Not AddrMsgID Then
               If ParseAddr (TmpStr, TmpAddr, JM^. Orig) Then;
             End;
         1 : Begin {Dest}
               FillChar (TmpAddr, SizeOf (TmpAddr), #0);
               FillChar (JM^. Dest, SizeOf (JM^. Dest), #0);
               SetLength (TmpStr, SubPtr^. DataLen And $ff);
               If Length (TmpStr) > 128 Then SetLength (TmpStr, 128);

               Move (SubPtr^. Data, TmpStr [1], Length (TmpStr));
               If ParseAddr (TmpStr, TmpAddr, JM^. Dest) Then;
             End;
         2 : Begin {MsgFrom}
               JM^. MsgFrom [0] := Chr (SubPtr^. DataLen And $ff);
               If Ord (JM^. MsgFrom [0] ) > 65 Then JM^. MsgFrom [0] := #65;
               Move (SubPtr^. Data, JM^. MsgFrom [1], Ord (JM^. MsgFrom [0] ));
             End;
         3 : Begin {MsgTo}
               JM^. MsgTo [0] := Chr (SubPtr^. DataLen And $ff);
               If Ord (JM^. MsgTo [0] ) > 65 Then JM^. MsgTo [0] := #65;
               Move (SubPtr^. Data, JM^. MsgTo [1], Ord (JM^. MsgTo [0] ));
             End;
         4 : Begin {MsgId}
               SetLength (TmpStr, SubPtr^. DataLen And $ff);
               If Length (TmpStr) > 240 Then SetLength (TmpStr, 240);
               Move (SubPtr^. Data, TmpStr [1], Length (TmpStr));
               AddTxtSub (#1'MSGID: ' + TmpStr);
               If ParseAddr (ExtractWord (1, TmpStr, [' ']), TmpAddr, JM^. Orig) Then;
               AddrMsgID := True;
             End;
         5 : Begin {Reply}
               SetLength (TmpStr, SubPtr^. DataLen And $ff);
               If Length (TmpStr) > 240 Then SetLength (TmpStr, 240);
               Move (SubPtr^. Data, TmpStr [1], Length (TmpStr));
               AddTxtSub (#1'REPLY: ' + TmpStr);
             End;
         6 : Begin {MsgSubj}
               JM^. MsgSubj [0] := Chr (SubPtr^. DataLen And $ff);
               If Ord (JM^. MsgSubj [0] ) > 100 Then JM^. MsgSubj [0] := #100;
               Move (SubPtr^. Data, JM^. MsgSubj [1], Ord (JM^. MsgSubj [0] ));
             End;
         7 : Begin {PID}
               SetLength (TmpStr, SubPtr^. DataLen And $ff);
               If Length (TmpStr) > 240 Then SetLength (TmpStr, 240);
               Move (SubPtr^. Data, TmpStr [1], Length (TmpStr));
               AddTxtSub (#1'PID: ' + TmpStr);
             End;
         8 : Begin {VIA}
               SetLength (TmpStr, SubPtr^. DataLen And $ff);
               If Length (TmpStr) > 240 Then SetLength (TmpStr, 240);
               Move (SubPtr^. Data, TmpStr [1], Length (TmpStr));
               AddTxtSub (#1'Via ' + TmpStr);
             End;
         9 : Begin {File attached}
               If IsFAttach Then
               Begin
                 JM^. MsgSubj [0] := Chr (SubPtr^. DataLen And $ff);
                 If Ord (JM^. MsgSubj [0] ) > 100 Then JM^. MsgSubj [0] := #100;
                 Move (SubPtr^. Data, JM^. MsgSubj [1], Ord (JM^. MsgSubj [0] ));
               End
             End;

         11: Begin {File request}
               If IsFileReq Then
               Begin
                 JM^. MsgSubj [0] := Chr (SubPtr^. DataLen And $ff);
                 If Ord (JM^. MsgSubj [0] ) > 100 Then JM^. MsgSubj [0] := #100;
                 Move (SubPtr^. Data, JM^. MsgSubj [1], Ord (JM^. MsgSubj [0] ));
               End
             End;

       2000: Begin {Unknown kludge}
               SetLength (TmpStr, SubPtr^. DataLen And $ff);
               If Length (TmpStr) > 240 Then SetLength (TmpStr, 240);
               Move (SubPtr^. Data, TmpStr [1], Length (TmpStr));
               AddTxtSub (#1 + TmpStr);
             End;
       2001: Begin {SEEN-BY}
               SetLength (TmpStr, SubPtr^. DataLen And $ff);
               If Length (TmpStr) > 240 Then SetLength (TmpStr, 240);
               Move (SubPtr^. Data, TmpStr [1], Length (TmpStr));
               AddTxtSub (#1'SEEN-BY: ' + TmpStr);
             End;

       2002: Begin {PATH}
               SetLength (TmpStr, SubPtr^. DataLen And $ff);
               If Length (TmpStr) > 240 Then SetLength (TmpStr, 240);
               Move (SubPtr^. Data, TmpStr [1], Length (TmpStr));
               AddTxtSub (#1'PATH: ' + TmpStr);
             End;

       2003: Begin {FLAGS}
               SetLength (TmpStr, SubPtr^. DataLen And $ff);
               If Length (TmpStr) > 240 Then SetLength (TmpStr, 240);
               Move (SubPtr^. Data, TmpStr [1], Length (TmpStr));
               AddTxtSub (#1'FLAGS: ' + TmpStr);
             End;
        End;
      End;
    End;
  End;
End;

Procedure JamMsgObj. MsgTxtStartUp;
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;
End;

Function JamMsgObj. GetString (MaxLen: Word): String;
Var
  WPos                 : LongInt;
  WLen                 : Byte;
  StrDone, StartSoft   : Boolean;
  CurrLen              : Word;
  TmpCh                : Char;
  Tmp                  : String;

Begin
  StrDone := False;
  Tmp := Replicate (' ', 255);
  CurrLen := 0;
  WPos := 0;
  WLen := 0;
  StartSoft := LastSoft;
  LastSoft := True;
  TmpCh := GetChar;

  While ((Not StrDone) And (CurrLen < MaxLen) And (Not EOM)) Do
  Begin
    Case TmpCh Of
      #$00:;
      #$0D: Begin
              StrDone := True;
              LastSoft := False;
            End;
      #$8D:;
      #$0a:;
      #$20: Begin
              If ((CurrLen <> 0) Or (Not StartSoft)) Then
              Begin
                Inc (CurrLen);
                WLen := CurrLen;
                Tmp [CurrLen] := TmpCh;
                WPos := JM^. TxtPos;
              End Else
                StartSoft := False;
            End;
    Else
      Inc (CurrLen);
      Tmp [CurrLen] := TmpCh;
    End;

    If Not StrDone Then TmpCh := GetChar;
  End;

  If StrDone Then SetLength (Tmp, CurrLen) Else
  If EOM Then SetLength (Tmp, CurrLen) Else
  Begin
    If WLen = 0 Then
    Begin
      SetLength (Tmp, CurrLen);
      Dec (JM^. TxtPos);
    End Else
    Begin
      SetLength (Tmp, WLen);
      JM^. TxtPos := WPos;
    End;
  End;
  GetString := Tmp;
End;

Function JamMsgObj. EOM: Boolean;
Begin
  EOM := (((JM^. TxtPos < MsgHdr^. JamHdr. TextOfs) Or
           (JM^. TxtPos > JM^. TxtEnd)) And
           (JM^. TxtPos >= 0));
End;

Function JamMsgObj. WasWrap: Boolean;
Begin
  WasWrap := LastSoft;
End;

Function JamMsgObj. GetFrom: String; {Get from name on current msg}
Begin
  GetFrom := JM^. MsgFrom;
End;

Function JamMsgObj. GetTo: String; {Get to name on current msg}
Begin
  GetTo := JM^. MsgTo;
End;

Function JamMsgObj. GetSubj: String; {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. GetDate: String; {Get date of current msg}
Begin
  GetDate := JM^. MsgDate;
End;

Function JamMsgObj. GetTime: String; {Get time of current msg}
Begin
  GetTime := 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} JM^. CurrMsgNum;
End;

Procedure JamMsgObj. GetOrig (Var Addr: AddrType); {Get origin address}
Begin
  Addr := JM^. Orig;
End;

Procedure JamMsgObj. GetDest (Var Addr: AddrType); {Get destination address}
Begin
  Addr := 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. IsDirect: Boolean;
Begin
  IsDirect := (MsgHdr^. JamHdr. Attr1 And Jam_Direct) <> 0;
End;

Function JamMsgObj. IsHold: Boolean;
Begin
  IsHold := (MsgHdr^. JamHdr. Attr1 And Jam_Hold) <> 0;
End;

Const
  oPos : LongInt = 0;

Procedure JamMsgObj. SeekCurMsg;
Var
  IdxLoc                : LongInt;
  NumRead               : SysInt;

Begin
  {
  IdxLoc := JM^. CurrMsgNum - JM^. BaseHdr. BaseMsgNum;

  If (IdxLoc < JM^. IdxStart) Or (IdxLoc >= (JM^. IdxStart + JM^. IdxRead))
  Then} Error := ReReadIdx (IdxLoc)
  {Else Error := 0};

  If oPos <> JamIdx^ [IdxLoc - JM^. IdxStart]. HdrLoc + 52 Then
  Begin
    oPos := JamIdx^ [IdxLoc - JM^. IdxStart]. HdrLoc + 52;
    shSeekFile (JM^. HdrFile, oPos);
    shRead (JM^. HdrFile, MsgHdr^. JamHdr. Attr1, 4, NumRead);
  End;

  If shIOResult <> 0 Then;
End;

Function JamMsgObj. GetMsgNumRelative: LongInt;
Var
  sNum            : LongInt;

Begin
  If NeedGetTotal Then
  Begin
    TotalMsgsNumber := 0;
    GetTotalMsgs;
  End;

  If NeedReCount Then
  Begin
    sNum := JM^. CurrMsgNum;
    SeekOver := False;
    SeekFirst (StartMsgNumber);
    SeekCurMsg;
    CurRelative := 1;
    IncRelative := False;

    While (JM^. CurrMsgNum <> sNum) And SeekFound Do
    Begin
      SeekCurMsg;
      If (MsgHdr^. JamHdr. Attr1 And Jam_Deleted) = 0 Then Inc (CurRelative);
      SeekNext;
    End;

    SeekFirst (sNum);
    MsgStartUp;

    NeedReCount := False;
    IncRelative := True;
  End;

  GetMsgNumRelative := CurRelative;
End;

Procedure JamMsgObj. GetTotalMsgs;
Var
  sNum, Last      : LongInt;
  SomeFound       : Boolean;

Begin
  sNum := JM^. CurrMsgNum;
  IncRelative := False;
  SeekFirst (JM^. BaseHdr. BaseMsgNum + shFileSize (JM^. IdxFile) - 1);
  SomeFound := False;

  While SeekFound Do
  Begin
    SeekCurMsg;
    If (MsgHdr^. JamHdr. Attr1 And Jam_Deleted) = 0 Then
    Begin
      Inc (TotalMsgsNumber);
      If TotalMsgsNumber = 1 Then
      Begin
        EndMsgNumber := JM^. CurrMsgNum;
        SomeFound := True;
      End;

      Last := JM^. CurrMsgNum;
    End;
    SeekPrior;
  End;

  StartMsgNumber := Last;

  If Not SomeFound Then
  Begin
    StartMsgNumber := 0;
    EndMsgNumber := 0;
  End;

  NeedReCount := True;
  IncRelative := True;
  If sNum = 0 Then sNum := StartMsgNumber;
  SeekFirst (sNum);
  {MsgStartUp;}
  NeedGetTotal := False;
End;

Procedure JamMsgObj. SeekFirst (MsgNum: LongInt); {Start msg shSeekFile}
Begin
  If ((JM^. CurrMsgNum <> MsgNum) or NeedReSeek or SeekOver) and (MsgNum > 0) Then
  Begin
    NeedReSeek := False;
    JM^. CurrMsgNum := MsgNum + 1;
    NeedReCount := True;
    SeekOver := False;
    If JM^. CurrMsgNum < (JM^. BaseHdr. BaseMsgNum - 1) Then
       JM^. CurrMsgNum := JM^. BaseHdr. BaseMsgNum - 1;
    SeekPrior;
    If TotalMsgsNumber > 0 Then SeekOver := False;
  End;
End;

Procedure JamMsgObj. SeekNext; {Find next matching msg}
Var
  IdxLoc, oNum, HighMsg : LongInt;

Label
  Loop,
  Loop1;

Begin
  Loop:

  If IncRelative Then
  If CurRelative >= TotalMsgsNumber Then
  Begin
    SeekOver := True;
    Exit;
  End;

  oNum := JM^. CurrMsgNum;
  HighMsg := GetHighMsgNumDir;
  SeekOver := False;
  If JM^. CurrMsgNum <= HighMsg Then Inc (JM^. CurrMsgNum);
  Error := ReReadIdx (IdxLoc);

  If JM^. CurrMsgNum <= HighMsg Then
  Begin
    While (((JamIdx^ [IdxLoc - JM^. IdxStart]. HdrLoc <= 0) Or
          (JamIdx^ [IdxLoc - JM^. IdxStart]. MsgToCrc = -1)) And
          (JM^. CurrMsgNum <= HighMsg))
    Do Begin
      Inc (JM^. CurrMsgNum);
      Error := ReReadIdx (IdxLoc);
    End;

    If JM^. CurrMsgNum > HighMsg Then
    Begin
      JM^. CurrMsgNum := oNum;
      GoTo Loop1;
    End;

  End Else
  Begin
    Dec (JM^. CurrMsgNum);
    Loop1:
    Error := ReReadIdx (IdxLoc);
    SeekOver := True;
  End;

  If Not SeekOver And IncRelative Then
  Begin
    If ((JM^. CurrMsgNum < JM^. BaseHdr. BaseMsgNum) Or
        (JM^. CurrMsgNum > HighMsg)) Then
    Begin
      Dec (JM^. CurrMsgNum);
      SeekOver := True;
    End Else
    Begin
      SeekCurMsg;
      If JM^. CurrMsgNum = 0 Then Exit;
      If (MsgHdr^. JamHdr. Attr1 And Jam_Deleted) <> 0 Then GoTo Loop;
      Inc (CurRelative);
    End;
  End;
End;

Procedure JamMsgObj. SeekPrior;
Var
  IdxLoc, oMsgNum : LongInt;

Label
  Loop;

Begin
  oMsgNum := -1;
  Loop:

  SeekOver := False;
  If JM^. CurrMsgNum >= JM^. BaseHdr. BaseMsgNum Then Dec (JM^. CurrMsgNum);
  Error := ReReadIdx (IdxLoc);

  If JM^. CurrMsgNum >= StartMsgNumber Then
  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 Else
  Begin
    Inc (JM^. CurrMsgNum);
    Error := ReReadIdx (IdxLoc);
    SeekFirst (GetLowMsgNum);
    SeekOver := True;
    NeedReCount := True;
  End;

  If Not SeekOver Then
  Begin
    If (JM^. CurrMsgNum = 0) Or (JM^. CurrMsgNum = oMsgNum) Then Exit;
    SeekCurMsg;
    If (MsgHdr^. JamHdr. Attr1 And Jam_Deleted) <> 0 Then
    Begin
      oMsgNum := JM^. CurrMsgNum;
      GoTo Loop;
    End;

    If IncRelative Then Dec (CurRelative);
  End;
End;

Function JamMsgObj. SeekFound: Boolean;
Begin
  SeekFound :=
    ((JM^. CurrMsgNum >= JM^. BaseHdr. BaseMsgNum) And
     (JM^. CurrMsgNum <= EndMsgNumber)) And (Not SeekOver);
End;

Function JamMsgObj. GetMsgLoc: LongInt; {Msg location}
Begin
  GetMsgLoc := GetMsgNum;
End;

Procedure JamMsgObj. SetMsgLoc (ML: LongInt); {Msg location}
Begin
  JM^. CurrMsgNum := ML;
End;

Var
  yFound : Boolean;
  YourSavePos, OldPos : LongInt;

Procedure JamMsgObj. YoursFirst (Name: String; Handle: String);
Begin
  YourSavePos := shFilePos (JM^. IdxFile);
  shSeekFile (JM^. IdxFile, 0);
  JM^. NameCRC := JamStrCrc (Name);
  JM^. HdlCRC := JamStrCrc (Handle);
  OldPos := 0;
  YoursNext;
End;

Procedure JamMsgObj. YoursNext;
Type
  tjIdx = Array [0..JamIdxBufSize-1] Of JamIdxType;

Var
  jIdx                   : ^tjIdx;
  Counter, NumRead, sPos : SysInt;
  Attr1                  : LongInt;

Begin
  New (jIdx);

  While Not shEoF (JM^. IdxFile) And (shIOResult = 0) Do
  Begin
    If OldPos <> 0 Then shSeekFile (JM^. IdxFile, OldPos);

    sPos := shFilePos (JM^. IdxFile);
    shRead (JM^. IdxFile, jIdx^, JamIdxBufSize-1, NumRead);

    For Counter := 0 To NumRead Do
    If (jIdx^ [Counter]. MsgToCRC = JM^. NameCRC) or
       (jIdx^ [Counter]. MsgToCrc = JM^. HdlCRC) Then
    Begin
      shSeekFile (JM^. HdrFile, jIdx^ [Counter]. HdrLoc + 52);
      shRead (JM^. HdrFile, Attr1, SizeOf (Attr1), NumRead);
      If (Attr1 And Jam_Rcvd) <> 0 Then Continue;

      shSeekFile (JM^. HdrFile, jIdx^ [Counter]. HdrLoc);
      shRead (JM^. HdrFile, MsgHdr^, SizeOf (MsgHdr^), NumRead);
      JM^. CurrMsgNum := MsgHdr^. JamHdr. MsgNum;
      OldPos := sPos + Counter + 1;
      shSeekFile (JM^. IdxFile, OldPos);
      yFound := True;
      Dispose (jIdx);
      Exit;
    End;
  End;

  shSeekFile (JM^. IdxFile, YourSavePos);
  shRead (JM^. IdxFile, jIdx^, 1, NumRead);
  shSeekFile (JM^. HdrFile, jIdx^ [0]. HdrLoc);
  shRead (JM^. HdrFile, MsgHdr^, SizeOf (MsgHdr^), NumRead);
  JM^. CurrMsgNum := MsgHdr^. JamHdr. MsgNum;
  yFound := False;
  Dispose (jIdx);
End;

(*
Var
  Found                 : Boolean;
  IdxLoc, SubCtr        : LongInt;
  NumRead               : SysInt;
  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
        shSeekFile (JM^. HdrFile, JamIdx^ [IdxLoc - JM^. IdxStart].HdrLoc);
        Error := shIOResult;

        If Error = 0 Then              {Read message header}
        Begin
          shRead (JM^. HdrFile, MsgHdr^, SizeOf (MsgHdr^), NumRead);
          Error := shIOResult;
        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 Ord (JM^. MsgTo [0] ) > 65 Then JM^. MsgTo [0] := #65;
                    Move (SubPtr^. Data, JM^. MsgTo [1], Ord (JM^. MsgTo [0] ));
                    If ((UpString (JM^. MsgTo) = UpString (JM^. YourName)) Or
                        ((UpString (JM^. MsgTo) = UpString (JM^. YourHdl)) and (JM^. MsgTo <> '')))
                    Then Begin
                      Found := True;
                      Exit;
                    End;
                  End;
            End;
          End;
        End;
      End;
    End;

    Inc (JM^. CurrMsgNum);
  End;
  SeekOver := True;
End;
*)

Function JamMsgObj. YoursFound: Boolean;
Begin
  YoursFound := yFound;
  {
  YoursFound := ((JM^. CurrMsgNum >= JM^. BaseHdr. BaseMsgNum) And
                 (JM^. CurrMsgNum <= GetHighMsgNum));
  }
End;

Procedure JamMsgObj. StartNewMsg;
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 := DateStr (GetDosDate);
  JM^. MsgTime := TimeStr (GetDosDate);
End;

Function JamMsgObj. MsgBaseExists: Boolean;
Begin
  MsgBaseExists := (FileExists (JM^. MsgPath + '.JHR'));
End;

Function JamMsgObj. ReadIdx: Word;
Begin
  If JM^. IdxStart < 0 Then JM^. IdxStart := 0;
  shSeekFile (JM^. IdxFile, JM^. IdxStart);
  {!!!}
  FillChar (JamIdx^, SizeOf (JamIdx^), #0);

  shRead (JM^. IdxFile, JamIdx^, JamIdxBufSize, JM^. IdxRead);
  ReadIdx := shIOResult;
End;

Function JamMsgObj. WriteIdx: Word;
Begin
  shSeekFile (JM^. IdxFile, JM^. IdxStart);
  shWrite (JM^. IdxFile, JamIdx^, JM^. IdxRead);
  WriteIdx := shIOResult;
End;

Function JamMsgObj. OpenMsgBase: Word;
Var
  OpenError  : Word;
  i          : SysInt;

Begin
  JM^. LockCount := 0;

  If Not FileExists (JM^. MsgPath + '.JHR') Or
     Not FileExists (JM^. MsgPath + '.JDT') Or
     Not FileExists (JM^. MsgPath + '.JDX') Then
  Begin
    OpenMsgBase := 2;
    Exit;
  End;

  shAssign (JM^. HdrFile, JM^. MsgPath + '.JHR');
  shAssign (JM^. TxtFile, JM^. MsgPath + '.JDT');
  shAssign (JM^. IdxFile, JM^. MsgPath + '.JDX');
  FileMode := fmReadWrite + fmDenyNone;
  shReset (JM^. HdrFile, 1);
  OpenError := shIOResult;

  If OpenError = 0 Then
  Begin
    shSeekFile (JM^. HdrFile, 1);
    shRead (JM^. HdrFile, JM^. BaseHdr. Signature [2], SizeOf (JM^. BaseHdr) - 1, i);
    EndMsgNumber := JM^. BaseHdr. BaseMsgNum + gFileSize (JM^. MsgPath + '.JDX') - 1;
    OpenError := shIOResult;
  End;

  If OpenError = 0 Then
  Begin
    FileMode := fmReadWrite + fmDenyNone;
    shReset (JM^. TxtFile, 1);
    OpenError := shIOResult;
  End;

  If OpenError = 0 Then
  Begin
    FileMode := fmReadWrite + fmDenyNone;
    shReset (JM^. IdxFile, SizeOf (JamIdxType));
    OpenError := shIOResult;
  End;

  JM^. IdxStart := -10;
  JM^. IdxRead := 0;
  JM^. TxtBufStart := -10;
  JM^. TxtRead := 0;
  OpenMsgBase := OpenError;
End;

Function JamMsgObj. CloseMsgBase: Word;
Var
  CloseError    : Word;

Begin
  shClose (JM^. HdrFile);
  CloseError := shIOResult;
  shClose (JM^. TxtFile);
  If CloseError = 0 Then CloseError := shIOResult;
  shClose (JM^. IdxFile);
  If CloseError = 0 Then CloseError := shIOResult;
  CloseMsgBase := CloseError;
End;

Function JamMsgObj. CreateMsgBase (MaxMsg: Word; MaxDays: Word): Word;
Var
  TmpHdr         : ^JamHdrType;
  CreateError, i : Word;

Begin
  CreateError := 0;
  i := PosLastChar ('\', JM^. MsgPath);

  If i > 0 Then
  If Not MakePath (Copy (JM^. MsgPath, 1, i)) Then CreateError := 0;

  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 := ToUnixDate (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 shIOResult <> 0 Then;
  End;
  CreateMsgBase := CreateError;
End;

Procedure JamMsgObj. SetMailType (MT: MsgMailType);
Begin
  JM^. MailType := MT;
End;

Function JamMsgObj. GetSubArea: Word;
Begin
  GetSubArea := 0;
End;

Procedure JamMsgObj. ReWriteHdr;
Var
  IdxLoc, Ofs, Len, IdxPos : LongInt;
  DT                       : DateTime;
  oHdr                     : ^HdrType;

Begin
  If LockMsgBase Then Error := 0 Else Error := 5;
  IdxPos := shFilePos (JM^. IdxFile);
  Error := ReReadIdx (IdxLoc);
  New (oHdr); oHdr^ := MsgHdr^;

  SeekCurMsg;
  Ofs := MsgHdr^. JamHdr. TextOfs;
  Len := MsgHdr^. Jamhdr. TextLen;

  MsgHdr^ := oHdr^;
  Dispose (oHdr);

  If Error = 0 Then
  Begin
    JamIdx^ [IdxLoc - JM^. IdxStart]. HdrLoc := shFileSize (JM^. HdrFile);
    Error := WriteIdx;
  End;

  If Error = 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
      mmtNormal        : SetAttr1 (Jam_TypeLocal, True);
      mmtEchoMail      : SetAttr1 (Jam_TypeEcho, True);
      mmtNetMail       : SetAttr1 (Jam_TypeNet, True);
    End;

    MsgHdr^. JamHdr. Rev := 1;
    MsgHdr^. JamHdr. DateArrived := ToUnixDate (GetDosDate); {Get date processed}
    DT. Year := Str2Long (Copy (JM^. MsgDate, 7, 2)); {Convert date written}
    DT. Month := Str2Long (Copy (JM^. MsgDate, 1, 2));
    DT. Day := Str2Long (Copy (JM^. MsgDate, 4, 2));
    If DT. Year < 80 Then Inc (DT. Year, 2000) Else Inc (DT. Year, 1900);
    DT. Sec := 0;
    DT. Hour := Str2Long (Copy (JM^. MsgTime, 1, 2));
    DT. Min := Str2Long (Copy (JM^. MsgTime, 4, 2));
    MsgHdr^. JamHdr. DateWritten := DateTime2UnixDate (DT);

    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
    If IsFileReq
    Then AddSubField (11, JM^. MsgSubj)
    Else 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));

    MsgHdr^. JamHdr. TextOfs := Ofs;
    MsgHdr^. Jamhdr. TextLen := Len;

    shSeekFile (JM^. HdrFile, shFileSize (JM^. HdrFile)); {shSeekFile to end of .jhr file}
  End;

  If Error = 0 Then
  Begin
    shWrite (JM^. HdrFile, MsgHdr^, SizeOf (MsgHdr^. JamHdr) + MsgHdr^. JamHdr. SubFieldLen);
    Inc (JM^. BaseHdr. ModCounter);
    shSeekFile (JM^. IdxFile, IdxPos);
  End;

  If UnLockMsgBase Then;
End;

Procedure JamMsgObj. DeleteMsg (CarePos: Boolean);
Var
  DelError         : Word;
  IdxLoc           : LongInt;
  IsBegin, IsEnd   : Boolean;

Begin
  If (MsgHdr^. JamHdr. Attr1 And Jam_Deleted) = 0 Then
  Begin
    If LockMsgBase Then DelError := 0 Else DelError := 5;

    If DelError = 0 Then
    Begin
      SetAttr1 (Jam_Deleted, True);
      {Dec (JM^. BaseHdr. ActiveMsgs);}
      DelError := ReReadIdx (IdxLoc);
      Dec (TotalMsgsNumber);
    End;

    If DelError = 0 Then
    Begin
      shSeekFile (JM^. HdrFile, JamIdx^ [IdxLoc - JM^. IdxStart]. HdrLoc + 52);
      shWrite (JM^. HdrFile, MsgHdr^. JamHdr. Attr1, 4);
    End
    {ReWriteHdr};

    If UnLockMsgBase Then;
  End;

  If DelError = 0 Then
  Begin
    IsBegin := (JM^. CurrMsgNum = StartMsgNumber);
    IsEnd   := (JM^. CurrMsgNum = EndMsgNumber);

    NeedGetTotal := True;
    NeedReCount := True;
    NeedReSeek := True;

    If CarePos Then
    Begin
      TotalMsgsNumber := 0;
      GetTotalMsgs;
      If IsBegin Then SeekFirst (StartMsgNumber) Else
      If IsEnd   Then SeekFirst (EndMsgNumber)
      Else SeekFirst (JM^. CurrMsgNum);
    End;
  End;

End;

Function JamMsgObj. NumberOfMsgs: LongInt;
Begin
  NumberOfMsgs := {JM^. BaseHdr. ActiveMsgs} TotalMsgsNumber;
End;

Function JamMsgObj. FindLastRead (Var LastFile: shFile; UNum: LongInt): LongInt;
Const
  LastSize = 100;

Type
  LastArray = Array [1..LastSize] Of JamLastType;

Var
  LastBuf             : ^LastArray;
  LastError, i        : Word;
  NumRead             : SysInt;
  Found               : Boolean;
  LastStart           : LongInt;

Begin
  FindLastRead := -1;
  Found := False;
  New (LastBuf);
  shSeekFile (LastFile, 0);
  LastError := shIOResult;

  While ((Not shEoF (LastFile)) And (LastError = 0) And (Not Found)) Do
  Begin
    LastStart := shFilePos (LastFile);
    shRead (LastFile, LastBuf^, LastSize, NumRead);
    LastError := shIOResult;

    For i := 1 To NumRead Do
    If LastBuf^ [i].UserNum = UNum Then
    Begin
      Found := True;
      FindLastRead := LastStart + i - 1;
    End;
  End;

  Dispose (LastBuf);
End;

Function JamMsgObj. GetLastRead (UNum: LongInt): LongInt;
Var
  RecNum        : LongInt;
  LastFile      : shFile;
  TmpLast       : JamLastType;
  i             : SysInt;

Begin
  shAssign (LastFile, JM^. MsgPath + '.JLR');
  FileMode := fmReadWrite + fmDenyNone;
  shReset (LastFile, SizeOf (JamLastType));
  If shIOResult <> 0 Then shReWrite (LastFile, 1);
  Error := shIOResult;
  RecNum := FindLastRead (LastFile, UNum);

  If RecNum >= 0 Then
  Begin
    shSeekFile (LastFile, RecNum);
    If Error = 0 Then
    Begin
      shRead (LastFile, TmpLast, 1, i);
      Error := shIOResult;
      GetLastRead := TmpLast. HighRead;
    End;
  End Else
    GetLastRead := 0;

  shClose (LastFile);
  Error := shIOResult;
End;

Procedure JamMsgObj. SetLastRead (UNum: LongInt; LR: LongInt);
Var
  RecNum        : LongInt;
  LastFile      : shFile;
  TmpLast       : JamLastType;
  i             : SysInt;

Begin
  shAssign (LastFile, JM^. MsgPath + '.JLR');
  FileMode := fmReadWrite + fmDenyNone;
  shReset (LastFile, SizeOf (JamLastType));
  Error := shIOResult;
  RecNum := FindLastRead (LastFile, UNum);

  If RecNum >= 0 Then
  Begin
    shSeekFile (LastFile, RecNum);
    If Error = 0 Then
    Begin
      shRead (LastFile, TmpLast, 1, i);
      Error := shIOResult;
      TmpLast. HighRead := LR;
      TmpLast. LastRead := LR;
      If Error = 0 Then
      Begin
        shSeekFile (LastFile, RecNum);
        Error := shIOResult;
      End;
      If Error = 0 Then
      Begin
        shWrite (LastFile, TmpLast, 1);
        Error := shIOResult;
      End;
    End;
  End Else
  Begin
    TmpLast. UserNum := UNum;
    TmpLast. HighRead := Lr;
    TmpLast. NameCrc := JamStrCrc (UName);
    TmpLast. LastRead := Lr;
    shSeekFile (LastFile, shFileSize (LastFile));
    Error := shIOResult;
    If Error = 0 Then
    Begin
      shWrite (LastFile, TmpLast, 1);
      Error := shIOResult;
    End;
  End;

  shClose (LastFile);
  Error := shIOResult;
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;
  i         : SysInt;
*)
Begin
  LockMsgBase := True;
(*
  LockError := 0;
  If JM^. LockCount = 0 Then
  Begin
    If LockError = 0 Then LockError := shLock (JM^. HdrFile, 0, 1);
    If LockError = 0 Then
    Begin
      shSeekFile (JM^. HdrFile, 0);
      LockError := shIOResult;
    End;
    If LockError = 0 Then
    Begin
      shRead (JM^. HdrFile, JM^. BaseHdr , SizeOf (JM^. BaseHdr), i);
      LockError := shIOResult;
    End;
  End;
  Inc (JM^. LockCount);
  LockMsgBase := (LockError = 0);
*)
End;

Function JamMsgObj. UnLockMsgBase: Boolean;
(*
Var
  LockError: Word;
*)
Begin
  UnLockMsgBase := True;
(*
  LockError := 0;
  If JM^. LockCount > 0 Then Dec (JM^. LockCount);
  If JM^. LockCount = 0 Then
  Begin
    If LockError = 0 Then LockError := UnLockFile (JM^. HdrFile, 0, 1);
    If LockError = 0 Then
    Begin
      shSeekFile (JM^. HdrFile, 0);
      LockError := shIOResult;
    End;
    If LockError = 0 Then
    Begin
      shWrite (JM^. HdrFile, JM^. BaseHdr, SizeOf (JM^. BaseHdr));
      LockError := shIOResult;
    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
  IdxLoc := JM^. CurrMsgNum - JM^. BaseHdr. BaseMsgNum;

  If ((IdxLoc < JM^. IdxStart) Or (IdxLoc >= (JM^. IdxStart + JM^. IdxRead))) Then
  Begin
    JM^. IdxStart := IdxLoc - {30} JamIdxBufSize;
    If JM^. IdxStart < 0 Then JM^. IdxStart := 0;
    ReReadIdx := ReadIdx;
  End Else
    ReReadIdx := 0;
End;

Function JamMsgObj. GetLowMsgNum: LongInt;
Begin
  GetLowMsgNum := StartMsgNumber;
End;

End.
