Unit MKMsgSqu;

{$IFDEF MSDOS}
{$V-,S-,F+,R-,O+,A-}
{$ENDIF}
{$I-}

Interface

Uses
  MkGlobT,
  MkMsgAbs,
  MkFile,
  tMisc,
  Objects,
  tGlob,
{$IFNDEF WIN32}
  DOS,
{$ELSE}
  OpCrt,
  SysUtils,
{$ENDIF}
  MKDos;

Const
  SqHdrId     = $AFAE4453;
  SqLinkNext  = 0;
  SqLinkPrev  = 1;
  SqNullFrame = 0;
  SqFrameMsg  = 0;
  SqFrameFree = 1;
  SqFrameRLE  = 2;
  SqFrameLZW  = 3;
  SqFromSize  = 36;
  SqToSize    = 36;
  SqSubjSize  = 72;
  SqMaxReply  = 10;

Type
  SqBaseType = Record
    Len,                                 {Length of this record}
    Rsvd1      : Word;                   {Future use}
    NumMsg,                              {Number of messages}
    HighMsg,                             {Highest msg}
    SkipMsg,                             {# of msgs to keep in beginning of area}
    HighWater,                           {High water UMsgId}
    Uid        : LongInt;                {Next UMsgId}
    Base       : String [79];            {Base name of Squish file}
    BeginFrame,                          {Offset of first frame in file}
    LastFrame,                           {Offset of last frame in file}
    FirstFree,                           {Offset of first free frame in file}
    LastFree,                            {Offset of last free frame in file}
    EndFrame,                            {Pointer to end of file}
    MaxMsg     : LongInt;                {Maximum number of messages}
    KeepDays,                            {Maximum age of messages}
    SqHdrSize  : Word;                   {Size of frame header}
    Rsvd2      : Array [1..124] Of Byte; {Future use}
  End;

  SqFrameHdrType = Record
    Id,                      {Must equal SqHdrId}
    NextFrame,               {Next msg frame}
    PrevFrame,               {Prior msg frame}
    FrameLength,             {Length of this frame not counting header}
    MsgLength,               {Length of message}
    ControlLength : LongInt; {Length of control information}
    FrameType,               {Type of message frame}
    Rsvd          : Word;    {Future use}
  End;

  SqMsgHdrType = Record
    Attr         : LongInt;                 {Msg attribute}
    MsgFrom      : String [SqFromSize - 1]; {Nul Term from name}
    MsgTo        : String [SqToSize - 1];   {Nul term to name}
    Subj         : String [SqSubjSize - 1]; {Nul term subject}
    Orig,                                   {Origin address}
    Dest         : AddrType;                {Destination address}
    DateWritten,                            {Date/Time msg written}
    DateArrived  : LongInt;                 {Date/Time msg arrived here}
    UtcOffset    : Word;                    {Minutes offset from UTC}
    ReplyTo      : LongInt;                 {Original msg}
    Replies      : Array [1..SqMaxReply]    {Replies}
                   Of LongInt;
    AzDate       : String [19];             {AsciiZ "Fido" style date}
  End;

  SqIdxType = Record
    Ofs,                {Offset of frame header}
    UMsgId,             {Unique message id}
    Hash    : LongInt;  {Hash of MsgTo name}
  End;

Const
{$IFNDEF MSDOS}
  SqIdxArraySize = 5200;
  SqTxtBufferSize = 34000;
{$ELSE}
  SqIdxArraySize = 3200;
  SqTxtBufferSize = 18000;
{$ENDIF}

  MaxFree = 500;
  SqBSize: Word = SizeOf (SqBaseType);
  SqFSize: Word = SizeOf (SqFrameHdrType);
  SqMSize: Word = SizeOf (SqMsgHdrType);
  SqISize: Word = SizeOf (SqIdxType);

Type
  SqIdxArrayType = Array [1..SqIdxArraySize] Of SqIdxType;

  SqIdxPtrType = ^SqIdxArrayType;

  FreeListType = Record
    FreePos, FreeSize  : LongInt;
  End;

  FreeArrayType = Array [1..MaxFree] Of FreeListType;

  SqInfoType = Record
    FN                          : String [80];
    MsgChars                    : Array [1..SqTxtBufferSize] Of Char;
    Error                       : Word;
    SqdFile, SqIFile            : shFile;
    SqBase                      : SqBaseType;
    SqBaseExtra                 : Array [1..100] Of Char;
    SqdOpened, SqiOpened        : Boolean;
    SqiAlloc                    : Word;
    Locked, FreeLoaded          : Boolean;
    HighestFree                 : Word;
    Frame                       : SqFrameHdrType;
    MsgHdr                      : SqMsgHdrType;
    Extra                       : Array [1..100] Of Char;
    TxtCtr                      : Word;
    MsgDone                     : Boolean;
    CurrIdx                     : Word;
    StrDate, StrTime            : String [8];
    CurrentFramePos, CurrentUID : LongInt;
    SName, SHandle              : String [35];
    HName, HHandle              : LongInt;
  End;

  SqMsgObj = Object (AbsMsgObj)
    SqInfo          : ^SqInfoType;
    SqIdx           : ^SqIdxArrayType;
    FreeArray       : ^FreeArrayType;
    SeekOver, Prev1 : Boolean;
    NullCount       : Byte;
    CLen            : LongInt;
    Constructor Init; {Initialize}
    Destructor Done; Virtual; {Done cleanup and dispose}
    Function  OpenMsgBase: Word; Virtual; {Open message base}
    Function  CloseMsgBase: Word; Virtual; {Close message base}
    Function  CreateMsgBase (MaxMsg: Word; MaxDays: Word): Word; Virtual;
    Function  MsgBaseExists: Boolean; Virtual;
    Procedure SetMsgPath (FN: String); Virtual; {Set filepath and name - no extension}
    Function  SqdOpen: Word; Virtual; {Open squish data file}
    Function  SqiOpen: Word; Virtual; {Open squish index file}
    Procedure SqdClose; Virtual; {Close squish data file}
    Procedure SqiClose; Virtual; {Close squish index file}
    Function  LockMsgBase: Boolean; Virtual; {Lock msg base}
    Function  UnLockMsgBase: Boolean; Virtual; {Unlock msg base}
    Procedure ReadBase; Virtual; {Read base data record}
    Procedure WriteBase; Virtual; {Write base data record}
    Function  GetBeginFrame: LongInt; Virtual; {Get beginning frame pos}
    Function  GetHighWater: LongInt; Virtual; {Get high water umsgid}
    Procedure ReadFrame (FPos: LongInt); Virtual; {Read frame at FPos}
    Procedure ReadVarFrame (Var Frame: SqFrameHdrType; FPos: LongInt); Virtual; {Read frame at FPos into Frame}
    Procedure WriteFrame (FPos: LongInt); Virtual; {Write frame at FPos}
    Procedure WriteVarFrame (Var Frame: SqFrameHdrType; FPos: LongInt); Virtual;
    Procedure UnlinkFrame (Var Frame: SqFrameHdrType); Virtual; {Unlink frame from linked list}
    Procedure LinkFrameNext (Var Frame: SqFrameHdrType; OtherFrame: LongInt;
              FramePos: LongInt); Virtual; {Link frame after other frame}
    Procedure KillMsg (MsgNum: LongInt); {Kill msg msgnum}
    Procedure KillExcess; {Kill msg in excess of limit}
    Procedure FindFrame (Var FL: LongInt; Var FramePos: LongInt); Virtual;
    Function  GetNextFrame: LongInt; Virtual; {Get next frame pos}
    Procedure ReadMsgHdr (FPos: LongInt); Virtual; {Read msg hdr for frame at FPos}
    Procedure WriteMsgHdr (FPos: LongInt); Virtual; {Read msg hdr for frame at FPos}
    Procedure WriteText (FPos: LongInt); Virtual; {Write text buffer for frame at Fpos}
    Function  SqHashName (Name: String): LongInt; Virtual; {Convert name to hash value}
    Procedure StartNewMsg; Virtual; {Initialize msg header}
    Function  GetFrom: String; Virtual; {Get message from}
    Function  GetTo: String; Virtual; {Get message to}
    Function  GetSubj: String; Virtual; {Get message subject}
    Procedure SetFrom (Str: String); Virtual; {Set message from}
    Procedure SetTo (Str: String); Virtual; {Set message to}
    Procedure SetSubj (Str: String); Virtual; {Set message subject}
    Procedure SetDate (Str: String); Virtual; {Set message date}
    Procedure SetTime (Str: String); Virtual; {Set message time}
    Function  GetDate: String; Virtual; {Get message date mm-dd-yy}
    Function  GetTime: String; Virtual; {Get message time hh:mm}
    Function  GetRefer: LongInt; Virtual; {Get reply to of current msg}
    Procedure SetRefer (Num: LongInt); Virtual; {Set reply to of current msg}
    Function  GetSeeAlso: LongInt; Virtual; {Get see also msg}
    Procedure SetSeeAlso (Num: LongInt); Virtual; {Set see also msg}
    Procedure ReadText (FPos: LongInt); Virtual;
    Function  GetChar: Char; Virtual;
    Function  GetString (MaxLen: Word): String; Virtual;
    Procedure GetOrig (Var Addr: AddrType); Virtual;
    Procedure SetOrig (Var Addr: AddrType); Virtual;
    Procedure GetDest (Var Addr: AddrType); Virtual;
    Procedure SetDest (Var Addr: AddrType); Virtual;
    Function  GetNextSeeAlso: LongInt; Virtual; {Get see also msg}
    Function  EOM: Boolean; Virtual;
    Function  WasWrap: Boolean; Virtual;
    Procedure InitText; Virtual;
    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}
    Function  WriteMsg: Word; Virtual; {Write msg to msg base}
    Procedure ReadIdx; Virtual;
    Procedure WriteIdx; Virtual;
    Procedure SeekFirst (MsgNum: LongInt); Virtual; {Seeks to 1st msg >= MsgNum}
    Function  GetMsgNum: LongInt; Virtual;
    Function  GetMsgNumRelative: LongInt; Virtual;
    Procedure SeekNext; Virtual;
    Procedure SeekPrior; Virtual;
    Function  SeekFound: Boolean; Virtual;
    Function  NumberOfMsgs: LongInt; Virtual;
    Function  GetIdxFramePos: LongInt; Virtual;
    Function  GetIdxHash: LongInt; Virtual;
    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}
    Procedure SetAttr (St: Boolean; Mask: LongInt); Virtual; {Set attribute}
    Procedure SetLocal (St: Boolean); Virtual; {Set local status}
    Procedure SetRcvd (St: Boolean); Virtual; {Set received status}
    Procedure SetPriv (St: Boolean); Virtual; {Set priveledge vs public status}
    Procedure SetCrash (St: Boolean); Virtual; {Set crash netmail status}
    Procedure SetKillSent (St: Boolean); Virtual; {Set kill/sent netmail status}
    Procedure SetSent (St: Boolean); Virtual; {Set sent netmail status}
    Procedure SetFAttach (St: Boolean); Virtual; {Set file attach status}
    Procedure SetReqRct (St: Boolean); Virtual; {Set request receipt status}
    Procedure SetReqAud (St: Boolean); Virtual; {Set request audit status}
    Procedure SetRetRct (St: Boolean); Virtual; {Set return receipt status}
    Procedure SetFileReq (St: Boolean); Virtual; {Set file request status}
    Procedure MsgStartUp; Virtual; {Set up message}
    Procedure MsgTxtStartUp; Virtual; {Set up for msg text}
    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}
    Procedure LoadFree; Virtual; {Load freelist into memory}
    Procedure SetEcho (ES: Boolean); Virtual; {Set echo status}
    Function  IsEchoed: Boolean; Virtual; {Is current msg unmoved echomail msg}
    Function  GetLastRead (UNum: LongInt): LongInt; Virtual; {Get last read for user num}
    Procedure SetLastRead (UNum: LongInt; LR: LongInt); Virtual; {Set last read}
    Function  GetMsgLoc: LongInt; Virtual; {To allow reseeking to message}
    Procedure SetMsgLoc (ML: LongInt); Virtual; {Reseek to message}
    Function  IdxHighest: LongInt; Virtual; { *** }
    Procedure YoursFirst (Name: String; Handle: String); Virtual; {Seek your mail}
    Procedure YoursNext; Virtual; {Seek next your mail}
    Function  YoursFound: Boolean; Virtual; {Message found}
    Function  GetMsgDisplayNum: LongInt; Virtual; {Get msg number to display}
    Function  GetTxtPos: LongInt; Virtual; {Get indicator of msg text position}
    Procedure SetTxtPos (TP: LongInt); Virtual; {Set text position}
    Function  GetHighMsgNum: LongInt; Virtual; {Get highest msg number}
    Procedure GetTotalMsgs; Virtual;
  End;

  SqMsgPtr = ^SqMsgObj;

Implementation

Var
  Tmp   : String;

Const
  SqMsgPriv    = $00001;
  SqMsgCrash   = $00002;
  SqMsgRcvd    = $00004;
  SqMsgSent    = $00008;
  SqMsgFile    = $00010;
  SqMsgFwd     = $00020;
  SqMsgOrphan  = $00040;
  SqMsgKill    = $00080;
  SqMsgLocal   = $00100;
  SqMsgHold    = $00200;
  SqMsgXX2     = $00400;
  SqMsgFreq    = $00800;
  SqMsgRrq     = $01000;
  SqMsgCpt     = $02000;
  SqMsgArq     = $04000;
  SqMsgUrg     = $08000;
  SqMsgScanned = $10000;

Constructor SqMsgObj. Init;
Begin
  New (SqInfo);
  New (FreeArray);

  If ((SqInfo = Nil) Or (FreeArray = Nil)) Then
  Begin
    If SqInfo <> Nil Then Dispose (SqInfo);
    If FreeArray <> Nil Then Dispose (FreeArray);
    Fail; Exit;
  End;

  C := New (PCollection, Init (10, 10));
  FillChar (SqInfo^, SizeOf (SqInfo^), #0);
  CurMsg := 1;
End;

Destructor SqMsgObj. Done;
Begin
  If SqInfo^. SqdOpened Then SqdClose;
  If SqInfo^. SqiOpened Then SqiClose;
  If SqInfo^. SqIAlloc > 0 Then
  If SqIdx <> Nil Then FreeMem (SqIdx, SqInfo^. SqiAlloc * SizeOf (SqIdxType));
  Dispose (FreeArray);
  Dispose (SqInfo);
  If C <> Nil Then
  Begin
    While C^. Count > 0 Do C^. AtDelete (0);
    Dispose (C, Done);
  End;
End;

Procedure SqMsgObj. SetMsgPath (FN: String);
Begin
  SqInfo^. FN := {$IFNDEF WIN32} FExpand {$ELSE} ExpandFileName {$ENDIF} (FN);
End;

Function SqMsgObj. OpenMsgBase: Word;
Begin
  If SqiOpen = 0 Then
  Begin
    OpenMsgBase := SqdOpen;
    ReadIdx;
  End Else
    OpenMsgBase := 100;
End;

Function SqMsgObj. SqdOpen: Word;
Var
  NumRead : SysInt;

Begin
  If Not SqInfo^. SqdOpened Then
  Begin
    shAssign (SqInfo^. SqdFile, SqInfo^. FN + '.SQD');
    FileMode := fmReadWrite + fmDenyNone;

    If Not shReset (SqInfo^. SqdFile, 1)
    Then SqdOpen := shIOResult Else
    Begin
      SqInfo^. SqdOpened := True;
      SqdOpen := 0;

      If Not shRead (SqInfo^. SqdFile, SqInfo^. SqBase, 2, NumRead)
      Then SqdOpen := shIOResult Else
      Begin
        If SqInfo^. SqBase. Len = 0 Then SqInfo^. SqBase. Len := SqBSize;
        If SqInfo^. SqBase. Len > (SizeOf (SqBaseType) + 100) Then SqdOpen := 1001 Else
        Begin
          SqBSize := SqInfo^. SqBase. Len;
          ReadBase;
        End;
      End;
    End;
  End Else
    SqdOpen := 0;
End;

Function SqMsgObj. SqiOpen: Word;
Begin
  If Not SqInfo^. SqiOpened Then
  Begin
    If Not FileExists (SqInfo^. FN + '.SQI') Then
    Begin
      SqiOpen := 2;
      Exit;
    End;

    shAssign (SqInfo^. SqiFile, SqInfo^. FN + '.SQI');
    FileMode := fmReadWrite + fmDenyNone;

    If Not shReset (SqInfo^. SqiFile, SizeOf (SqIdxType))
    Then SqiOpen := shIOResult Else
    Begin
      SqInfo^. SqiOpened := True;
      SqiOpen := 0;
    End;

  End Else
    SqiOpen := 0;
End;

Function SqMsgObj. CloseMsgBase: Word;
Begin
  SqdClose;
  SqiClose;
  CloseMsgBase := 0;
End;

Function SqMsgObj. CreateMsgBase (MaxMsg: Word; MaxDays: Word): Word;
Var
  i     : Word;

Begin
  If Not SqInfo^. SqdOpened Then
  Begin
    i := PosLastChar ('\', SqInfo^. FN);
    If i > 0 Then
    If MakePath (Copy (SqInfo^. FN, 1, i)) Then;

    FillChar (SqInfo^. SqBase, SizeOf (SqInfo^. SqBase), 0);
    SqInfo^. SqBase. Len := 256;
    SqInfo^. SqBase. SqHdrSize := SqFSize;
    SqInfo^. SqBase. UID := 1;
    SqInfo^. SqBase. NumMsg := 0;
    SqInfo^. SqBase. Base := SqInfo^. FN;
    Str2Az (SqInfo^. FN, 78, SqInfo^. SqBase. Base);
    SqInfo^. SqBase. MaxMsg := MaxMsg;
    SqInfo^. SqBase. KeepDays := MaxDays;
    SqInfo^. SqBase. EndFrame := SqInfo^. SqBase. Len;
    CreateMsgBase := SaveFile (SqInfo^. FN + '.SQD', SqInfo^. SqBase, SqInfo^. SqBase. Len);
    If SaveFile (SqInfo^. FN + '.SQI', SqInfo^. SqBase, 0) = 0 Then;
    If SaveFile (SqInfo^. FN + '.SQL', SqInfo^. SqBase, 0) = 0 Then;
  End Else
    CreateMsgBase := 176;
End;

Function SqMsgObj. MsgBaseExists: Boolean;
Begin
  MsgBaseExists :=  FileExists (SqInfo^. FN + '.SQD');
End;

Procedure SqMsgObj. SqdClose;
Begin
  If SqInfo^. SqdOpened Then shClose (SqInfo^. SqdFile);
  If shIOResult <> 0 Then;
  SqInfo^. SqdOpened := False;
End;

Function SqMsgObj. LockMsgBase: Boolean; {Lock msg base}
Begin
  LockMsgBase := True;
  SqInfo^. Locked := True;
  ReadBase; ReadIdx;
  SqInfo^. FreeLoaded := False;
(*
  If Not SqInfo^. Locked Then
  Begin
    SqInfo^. Locked := shLock (SqInfo^. SqdFile, 0, 1) = 0;
    LockMsgBase := SqInfo^. Locked;
    ReadBase; ReadIdx;
    SqInfo^. FreeLoaded := False;
  End;
*)
End;

Function SqMsgObj. UnLockMsgBase: Boolean; {Unlock msg base}
Begin
  UnLockMsgBase := True;
  SqInfo^. Locked := False;
  WriteBase; WriteIdx;
(*
  If SqInfo^. Locked Then
  Begin
    SqInfo^. Locked := Not UnLockFile (SqInfo^. SqdFile, 0, 1) < 2;
    UnLockMsgBase := Not SqInfo^. Locked;
  End;
*)
End;

Procedure SqMsgObj. SqiClose;
Begin
  If SqInfo^. SqiOpened Then shClose (SqInfo^. SqiFile);
  If shIOResult <> 0 Then;
  SqInfo^. SqiOpened := False;
End;

Procedure SqMsgObj. ReadBase;
Var
  NumRead: SysInt;

Begin
  shSeekFile (SqInfo^. SqdFile, 0);
  If Not shRead (SqInfo^. SqdFile, SqInfo^. SqBase, SqBSize, NumRead) Then SqInfo^. Error := shIOResult;
  If SqInfo^. SqBase. SqHdrSize = 0 Then SQInfo^. SqBase. SqHdrSize := SqFSize;
  SqFSize := SqInfo^. SqBase. SqHdrSize;
End;

Procedure SqMsgObj. WriteBase;
Begin
  shSeekFile (SqInfo^. SqdFile, 0);
  If Not shWrite (SqInfo^. SqdFile, SqInfo^. SqBase, SQBSize) Then SqInfo^. Error := shIOResult;
End;

Procedure SqMsgObj. StartNewMsg; {Initialize msg header}
Begin
  FillChar (SqInfo^. MsgHdr, SizeOf (SqInfo^. MsgHdr), 0);
  FillChar (SqInfo^. Frame, SizeOf (SqInfo^. Frame), 0);
  SqInfo^. TxtCtr := 0;
  SqInfo^. StrDate := '';
  SqInfo^. StrTime := '';
  Prev1 := False;
  CLen := 0;
End;

Function SqMsgObj. GetFrom: String; {Get message from}
Begin
  Tmp := SqInfo^. MsgHdr. MsgFrom;
  GetFrom := Az2Str (Tmp, 35);
End;

Function SqMsgObj. GetTo: String; {Get message to}
Begin
  Tmp := SqInfo^. MsgHdr. MsgTo;
  GetTo := Az2Str (Tmp, 35);
End;

Function SqMsgObj. GetSubj: String; {Get message subject}
Begin
  Tmp := SqInfo^. MsgHdr. Subj;
  GetSubj := Az2Str (Tmp, 72);
End;

Procedure SqMsgObj. SetFrom (Str: String); {Set message from}
Begin
  Str2Az (Str, 35, SqInfo^. MsgHdr. MsgFrom);
End;

Procedure SqMsgObj. SetTo (Str: String); {Set message to}
Begin
  Str2Az (Str, 35, SqInfo^. MsgHdr. MsgTo);
End;

Procedure SqMsgObj. SetSubj (Str: String); {Set message subject}
Begin
  Str2Az (Str, 72, SqInfo^. MSgHdr. Subj);
End;

Function SqMsgObj. GetDate: String; {Get message date mm-dd-yy}
Var
  TmpDate : LongInt;

Begin
  TmpDate := ((SqInfo^. MsgHdr. DateWritten ShR 16) +
              (SqInfo^. MsgHdr. DateWritten And $ffff) ShL 16);
  GetDate := DateStr (TmpDate);
End;

Function SqMsgObj. GetTime: String; {Get message time hh:mm}
Var
  TmpDate : LongInt;

Begin
  TmpDate :=  (SqInfo^. MsgHdr. DateWritten ShR 16) +
             ((SqInfo^. MsgHdr. DateWritten And $ffff) ShL 16);
  GetTime := Copy (TimeStr (TmpDate), 1, 5);
End;

Procedure SqMsgObj. SetDate (Str: String);
Begin
  SqInfo^. StrDate := Copy (Str, 1, 8);
End;

Procedure SqMsgObj. SetTime (Str: String);
Begin
  SqInfo^. StrTime := Copy (Str, 1, 8);
End;

Procedure SqMsgObj. GetOrig (Var Addr: AddrType);
Begin
  Addr := SqInfo^. MsgHdr. Orig;
End;

Procedure SqMsgObj. SetOrig (Var Addr: AddrType);
Begin
  SqInfo^. MsgHdr. Orig := Addr;
End;

Procedure SqMsgObj. GetDest (Var Addr: AddrType);
Begin
  Addr := SqInfo^. MsgHdr. Dest;
End;

Procedure SqMsgObj. SetDest (Var Addr: AddrType);
Begin
  SqInfo^. MsgHdr. Dest := Addr;
End;

Function SqMsgObj. SqHashName (Name: String): LongInt;
Var
  Hash, Tmp     : LongInt;
  Counter       : Word;

Begin
  Hash := 0;
  Counter := 1;

  While Counter <= Length (Name) Do
  Begin
    Hash := (Hash ShL 4) + Ord (LoCase (Name [Counter]));
    Tmp := Hash And $F0000000;
    If (Tmp <> 0) Then Hash := (Hash Or (Tmp ShR 24)) Or Tmp;
    Inc (Counter);
  End;

  SqHashName := Hash And $7fffffff;
End;

Procedure SqMsgObj. ReadFrame (FPos: LongInt); {Read frame at FPos}
Begin
  ReadVarFrame (SqInfo^. Frame, FPos);
End;

Procedure SqMsgObj. ReadVarFrame (Var Frame: SqFrameHdrType; FPos: LongInt); {Read frame at FPos}
Var
  NumRead : SysInt;

Begin
  shSeekFile (SqInfo^. SqdFile, FPos);
  SqInfo^. Error := shIOResult;

  If SqInfo^. Error = 0 Then
  If Not shRead (SqInfo^. SqdFile, Frame, SizeOf (SqFrameHdrType), NumRead)
  Then SqInfo^. Error := shIOResult;
End;

Procedure SqMsgObj. WriteFrame (FPos: LongInt); {Read frame at FPos}
Begin
  WriteVarFrame (SqInfo^. Frame, FPos);
End;

Procedure SqMsgObj. WriteVarFrame (Var Frame: SqFrameHdrType; FPos: LongInt); {Write frame at FPos}
Begin
  shSeekFile (SqInfo^. SqdFile, FPos);
  SqInfo^. Error := shIOResult;

  If SqInfo^. Error = 0 Then
  If Not shWrite (SqInfo^. SqdFile, Frame, SizeOf (SqFrameHdrType))
  Then SqInfo^. Error := shIOResult;
End;

Procedure SqMsgObj. UnlinkFrame (Var Frame: SqFrameHdrType);
Var
  TmpFrame      : SqFrameHdrType;

Begin
  If Frame. PrevFrame <> 0 Then
  Begin
    ReadVarFrame (TmpFrame, Frame. PrevFrame);
    TmpFrame. NextFrame := Frame. NextFrame;
    WriteVarFrame (TmpFrame, Frame. PrevFrame);
  End;

  If Frame. NextFrame <> 0 Then
  Begin
    ReadVarFrame (TmpFrame, Frame. NextFrame);
    TmpFrame. PrevFrame := Frame. PrevFrame;
    WriteVarFrame (TmpFrame, Frame. NextFrame);
  End;
End;

Procedure SqMsgObj. LoadFree;
Var
  i             : Word;
  TmpFrame      : SqFrameHdrType;
  TmpPos        : LongInt;

Begin
  For i := 1 To MaxFree Do
  With FreeArray^ [i] Do
  Begin
    FreePos := 0;
    FreeSize := 0;
  End;

  i := 0;
  SqInfo^. FreeLoaded := True;
  TmpPos := SqInfo^. SqBase. FirstFree;

  While ((TmpPos <> 0) And (i < MaxFree)) Do
  Begin
    ReadVarFrame (TmpFrame, TmpPos);
    Inc (i);
    FreeArray^ [i]. FreeSize := TmpFrame. FrameLength;
    FreeArray^ [i]. FreePos := TmpPos;
    TmpPos := TmpFrame. NextFrame;
  End;

  SqInfo^. HighestFree := i;
End;

Procedure SqMsgObj. FindFrame (Var FL: LongInt; Var FramePos: LongInt);
Var
  TmpFrame                      : SqFrameHdrType;
  BestFoundPos, BestFoundSize   : LongInt;
  BestIdx, i                    : Word;

Begin
  If Not SqInfo^. FreeLoaded Then LoadFree;
  BestFoundPos := 0;
  BestFoundSize := 0;

  For i := 1 To SqInfo^. HighestFree Do
  If (FreeArray^ [i].FreeSize > FL) Then
  If ((BestFoundSize = 0) Or (FreeArray^ [i].FreeSize < BestFoundSize)) Then
  Begin
    BestFoundSize := FreeArray^ [i]. FreeSize;
    BestFoundPos := FreeArray^ [i]. FreePos;
    BestIdx := i;
  End;

  FramePos := BestFoundPos;

  If FramePos <> 0 Then
  Begin
    ReadVarFrame (TmpFrame, FramePos);
    FreeArray^ [BestIdx]. FreePos := 0;
    FreeArray^ [BestIdx]. FreeSize := 0;
  End;

  If FramePos = 0 Then
  Begin
    FL := 0;
    FramePos := SqInfo^. SqBase. EndFrame;
  End Else
  Begin
    UnLinkFrame (TmpFrame);
    If TmpFrame. PrevFrame = 0 Then SqInfo^. SqBase. FirstFree := TmpFrame. NextFrame;
    If TmpFrame. NextFrame = 0 Then SqInfo^. SqBase. LastFree := TmpFrame. PrevFrame;
    FL := TmpFrame. FrameLength;
  End;
End;

Procedure SqMsgObj. LinkFrameNext (Var Frame: SqFrameHdrType;
          OtherFrame: LongInt; FramePos: LongInt);
Var
  TmpFrame  : SqFrameHdrType;

Begin
  If OtherFrame <> 0 Then
  Begin
    ReadVarFrame (TmpFrame, OtherFrame);
    TmpFrame. NextFrame := FramePos;
    Frame. PrevFrame := OtherFrame;
    WriteVarFrame (TmpFrame, OtherFrame);
  End;
End;

Procedure SqMsgObj. KillMsg (MsgNum: LongInt);
Var
  i                     : Word;
  KillPos, IndexPos     : LongInt;
  KillFrame, TmpFrame   : SqFrameHdrType;
  CurrMove              : LongInt;
  AlreadyLocked         : Boolean;
  FreeCtr               : Word;

Begin
  AlreadyLocked := SqInfo^. Locked;
  If Not AlreadyLocked Then LockMsgBase;

  If SqIdx = Nil Then SqInfo^. Error := 999 Else
  Begin
    i := 1;
    While ((i <= SqInfo^. SqBase. NumMsg) And (MsgNum <> SqIdx^ [i]. UMsgId)) Do Inc (i);

    If MsgNum = SqIdx^ [i]. UMsgId Then
    Begin
      IndexPos := i;
      KillPos := SqIdx^ [i]. Ofs;
      ReadVarFrame (KillFrame, KillPos);
      If KillFrame. PrevFrame = 0 Then SqInfo^. SqBase. BeginFrame := KillFrame. NextFrame;
      If KillFrame. NextFrame = 0 Then SqInfo^. SqBase. LastFrame := KillFrame. PrevFrame;
      KillFrame. FrameType := sqFrameFree;
      UnLinkFrame (KillFrame);

      If ((SqInfo^. SqBase. FirstFree = 0) Or
          (SqInfo^. SqBase. LastFree = 0)) Then
      Begin
        SqInfo^. SqBase. FirstFree := KillPos;
        SqInfo^. SqBase. LastFree := KillPos;
        KillFrame. PrevFrame := 0;
        KillFrame. NextFrame := 0;
      End Else
      Begin
        KillFrame. NextFrame := 0;
        KillFrame. PrevFrame := SqInfo^. SqBase. LastFree;
        ReadVarFrame (TmpFrame, SqInfo^. SqBase. LastFree);
        TmpFrame. NextFrame := KillPos;
        WriteVarFrame (TmpFrame, SqInfo^. SqBase. LastFree);
        SqInfo^. SqBase. LastFree := KillPos;
      End;

      WriteVarFrame (KillFrame, KillPos);
      FreeCtr := 1;
      While ((FreeCtr < MaxFree) And (FreeArray^ [FreeCtr].FreePos <> 0)) Do Inc (FreeCtr);

      If FreeArray^ [FreeCtr].FreePos = 0 Then
      Begin
        FreeArray^ [FreeCtr].FreePos := KillPos;
        FreeArray^ [FreeCtr].FreeSize := KillFrame. FrameLength;
      End;

      If FreeCtr > SqInfo^. HighestFree Then SqInfo^. HighestFree := FreeCtr;
      Dec (SqInfo^. SqBase. NumMsg);
      Dec (SqInfo^. SqBase. HighMsg);
      CurrMove := IndexPos;

      While CurrMove <= SqInfo^. SqBase. NumMsg Do
      Begin
        SqIdx^ [CurrMove] := SqIdx^ [CurrMove + 1];
        Inc (CurrMove);
      End;
    End;
  End;

  If Not AlreadyLocked Then UnlockMsgBase;
End;

Procedure SqMsgObj. ReadMsgHdr (FPos: LongInt); {Read msg hdr for frame at FPos}
Var
  NumRead: SysInt;

Begin
  shSeekFile (SqInfo^. SqdFile, FPos + SqFSize);
  SqInfo^. Error := shIOResult;

  If SqInfo^. Error = 0 Then
  If Not shRead (SqInfo^. SqdFile, SqInfo^. MsgHdr, SizeOf (SqMsgHdrType), NumRead)
  Then SqInfo^. Error := shIOResult;
End;

Procedure SqMsgObj. WriteMsgHdr (FPos: LongInt); {Read msg hdr for frame at FPos}
Begin
  shSeekFile (SqInfo^. SqdFile, FPos + SqFSize);
  SqInfo^. Error := shIOResult;

  If SqInfo^. Error = 0 Then
  If Not shWrite (SqInfo^. SqdFile, SqInfo^. MsgHdr, SizeOf (SqMsgHdrType))
  Then SqInfo^. Error := shIOResult;
End;

Procedure SqMsgObj. WriteText (FPos: LongInt); {Write text buffer for frame at Fpos}
Begin
  shSeekFile (SqInfo^. SqdFile, FPos + SqFSize + SqMSize);
  SqInfo^. Error := shIOResult;

  If SqInfo^. Error = 0 Then
  If Not shWrite (SqInfo^. SqdFile, SqInfo^. MsgChars, SqInfo^. TxtCtr)
  Then SqInfo^. Error := shIOResult;
End;

Function SqMsgObj. GetBeginFrame: LongInt; {Get beginning frame pos}
Begin
  GetBeginFrame := SqInfo^. SqBase. BeginFrame;
End;

Function SqMsgObj. GetNextFrame: LongInt; {Get next frame pos}
Begin
  GetNextFrame := SqInfo^. Frame. NextFrame;
End;

Procedure SqMsgObj. ReadText (FPos: LongInt);
Var
  i     : SysInt;

Begin
  shSeekFile (SqInfo^. SqdFile, FPos + SqFSize + SqMSize);
  SqInfo^. Error := shIOResult;

  If SqInfo^. Error = 0 Then
  Begin
    If SqInfo^. Frame. MsgLength > SqTxtBufferSize
    Then shRead (SqInfo^. SqdFile, SqInfo^. MsgChars, SqTxtBufferSize, i)
    Else shRead (SqInfo^. SqdFile, SqInfo^. MsgChars, SqInfo^. Frame. MsgLength, i);

    SqInfo^. Error := shIOResult;
  End;

  SqInfo^. TxtCtr := 1 {+ SqInfo^. Frame. ControlLength};
  SqInfo^. MsgDone := False;
  NullCount := 0;
  LastSoft := False;
End;

Procedure SqMsgObj. InitText;
Begin
  SqInfo^. TxtCtr := 0;
End;

Procedure SqMsgObj. DoString (Str: String); {Add string to message text}
Var
  i: Word;

Begin
  i := 1;
  While i <= Length (Str) Do
  Begin
    DoChar (Str [i]);
    Inc (i);
  End;
End;

Procedure SqMsgObj. DoChar (CH: Char); {Add character to message text}
Begin
  If SqInfo^. TxtCtr < SqTxtBufferSize Then
  Begin
    Inc (SqInfo^. TxtCtr);
    SqInfo^. MsgChars [SqInfo^. TxtCtr] := Ch;
  End;
End;

Procedure SqMsgObj. DoStringLn (Str: String);
{Add string and newline to msg text}

Begin
  If Prev1 And (Str [1] <> #1) Then
  Begin
    Prev1 := False;
    DoChar (#0);
  End Else
  If Str [1] = #1 Then
    Inc (CLen, Length (Str));

  DoString (Str);

  Prev1 := Str [1] = #1;
  If Not Prev1 Then DoChar (#13);
End;

Procedure SqMsgObj. KillExcess;
Var
  AlreadyLocked: Boolean;

Begin
  AlreadyLocked := SqInfo^. Locked;
  If Not AlreadyLocked Then LockMsgBase;

  If SqIdx = Nil Then SqInfo^. Error := 999 Else
  Begin
    If ((SqInfo^. SqBase. MaxMsg > 0) And (SqInfo^. SqBase. MaxMsg > SqInfo^. SqBase. SkipMsg)) Then
    While (SqInfo^. SqBase. NumMsg > SqInfo^. SqBase. MaxMsg)
    Do KillMsg (SqIdx^ [SqInfo^. SqBase. SkipMsg + 1].UMsgId);
  End;

  If Not AlreadyLocked Then UnlockMsgBase;
End;

Function SqMsgObj. WriteMsg: Word;
{Write msg to msg base}

Var
  MsgSize, FrameSize,
  FramePos, TmpDate   : LongInt;
  TmpFrame            : SqFrameHdrType;
  TmpDT               : {$IFDEF WINDOWS} TDateTime {$ELSE} DateTime {$ENDIF};
  TmpStr              : String;
  AlreadyLocked       : Boolean;

Begin
  DoChar (#0);
  TmpDT. Year := Str2Long (Copy (SqInfo^. StrDate, 7, 2));
  If TmpDT. Year > 79 Then Inc (TmpDT. Year, 1900) Else Inc (TmpDT. Year, 2000);
  TmpDT. Month := Str2Long (Copy (SqInfo^. StrDate, 1, 2));
  TmpDT. Day := Str2Long (Copy (SqInfo^. StrDate, 4, 2));
  TmpDt. Hour := Str2Long (Copy (SqInfo^. StrTime, 1, 2));
  TmpDt. Min := Str2Long (Copy (SqInfo^. StrTime, 4, 2));
  TmpDt. Sec := 0;
  TmpStr := FormattedDate (TmpDT, 'DD NNN YY  ') + Copy (SqInfo^. StrTime, 1, 5) + ':00';
  PackTime (TmpDT, TmpDate);
  SqInfo^. MsgHdr. DateWritten :=  (TmpDate ShR 16) + ((TmpDate And $ffff) ShL 16);
  TmpDate := GetDosDate;
  SqInfo^. MsgHdr. DateArrived := (TmpDate ShR 16) + ((TmpDate And $ffff) ShL 16);
  Str2AZ (TmpStr, 20, SqInfo^. MsgHdr. AZDate);
  AlreadyLocked := SqInfo^. Locked;
  If Not AlreadyLocked Then LockMsgBase;

  If SqInfo^. Locked Then
  Begin
    MsgSize := SqInfo^. TxtCtr + SqMSize;
    FrameSize := MsgSize;
    FindFrame (FrameSize, FramePos);

    If SqInfo^. SqBase. LastFrame <> 0 Then
    Begin
      ReadVarFrame (TmpFrame, SqInfo^. SqBase. LastFrame);
      TmpFrame. NextFrame := FramePos;
      WriteVarFrame (TmpFrame, SqInfo^. SqBase. LastFrame);
      TmpFrame. PrevFrame := SqInfo^. SqBase. LastFrame;
    End Else
    Begin
      SqInfo^. SqBase. BeginFrame := FramePos;
      TmpFrame. PrevFrame := 0;
    End;

    TmpFrame. Id := SqHdrId;
    TmpFrame. FrameType := SqFrameMsg;
    SqInfo^. SqBase. LastFrame := FramePos;
    TmpFrame. NextFrame := 0;
    TmpFrame. FrameLength := FrameSize;
    TmpFrame. MsgLength := MsgSize;
    TmpFrame. ControlLength := CLen + 1;

    If TmpFrame. FrameLength = 0 Then
    Begin
      TmpFrame. FrameLength := TmpFrame. MsgLength + 0; {slack to minimize free frames}
      SqInfo^. SqBase. EndFrame := FramePos + SqFSize + TmpFrame. FrameLength;
    End;

    If SqInfo^. SqBase. NumMsg >= SqInfo^. SqiAlloc Then
    Begin
      WriteIdx;
      ReadIdx;
    End;

    If SqIdx = Nil Then
    Begin
      SqInfo^. Error := 999;
      WriteMsg := 999;
    End Else
    Begin
      WriteVarFrame (TmpFrame, FramePos);
      WriteMsgHdr (FramePos);
      WriteText (FramePos);
      Inc (SqInfo^. SqBase. NumMsg);
      SqIdx^ [SqInfo^. SqBase. NumMsg]. Ofs := FramePos;
      SqIdx^ [SqInfo^. SqBase. NumMsg]. UMsgId := SqInfo^. SqBase. UID;
      Tmp := SqInfo^. MsgHdr. MsgTo;
      SqIdx^ [SqInfo^. SqBase. NumMsg].Hash := SqHashName (Az2Str (Tmp, 35));
      SqInfo^. MsgHdr. MsgTo := Tmp;
      Inc (SqInfo^. SqBase. UId);
      SqInfo^. SqBase. HighMsg := SqInfo^. SqBase. NumMsg;
      KillExcess;
      SqInfo^. CurrIdx := SqInfo^. SqBase. NumMsg;
      WriteMsg := 0;
    End;

    If Not AlreadyLocked Then UnLockMsgBase;
    C^. Insert (Pointer (SqInfo^. SqBase. NumMsg));
  End Else
    WriteMsg := 5;
End;

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

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

  While ((Not StrDone) And (CurrLen < MaxLen) And (Not SqInfo^. MsgDone)) Do
  Begin
    Case TmpCh Of
        #1 : If (SqInfo^. TxtCtr > 2) And (CurrLen > 1) Then
             Begin
               Dec (SqInfo^. TxtCtr);
               StrDone := True;
               LastSoft := False;
             End Else
             Begin
               Inc (CurrLen);
               Tmp [CurrLen] := TmpCh;
             End;
   #0, #13: 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 := SqInfo^. TxtCtr;
              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 SqInfo^. MsgDone Then SetLength (Tmp, CurrLen) Else
  Begin
    If WLen = 0 Then
    Begin
      SetLength (Tmp, CurrLen);
      Dec (SqInfo^. TxtCtr);
    End Else
    Begin
      SetLength (Tmp, WLen);
      SqInfo^. TxtCtr := WPos;
    End;
  End;

  If Copy (Tmp, 1, 9) = 'SEEN-BY: ' Then Tmp := #1 + Tmp;
  GetString := Tmp;
End;

Function SqMsgObj. EOM: Boolean;
Begin
  EOM := SqInfo^. MsgDone;
End;

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

Function SqMsgObj. GetChar: Char;
Begin
  If SqInfo^. MsgChars [SqInfo^. TxtCtr] = #0 Then Inc (NullCount);
  If (SqInfo^. TxtCtr >= SqInfo^. Frame. MsgLength) Or (NullCount >= 2) Then
  Begin
    GetChar := #0;
    SqInfo^. MsgDone := True;
  End Else
  Begin
    GetChar := SqInfo^. MsgChars [SqInfo^. TxtCtr];
    Inc (SqInfo^. TxtCtr);
  End;
End;

Function SqMsgObj. GetHighWater: LongInt; {Get high water umsgid}
Begin
  GetHighWater := LongInt (SqInfo^. SqBase. HighWater);
End;

Function SqMsgObj. GetHighMsgNum: LongInt; {Get highest msg number}
Begin
  GetHighMsgNum := SqInfo^. SqBase. HighMsg;
End;

Procedure SqMsgObj. ReadIdx;
Var
  NumRead  : SysInt;

Begin
  If SqInfo^. SqiAlloc > 0 Then
  If SqIdx <> Nil Then FreeMem (SqIdx, SqInfo^. SqiAlloc * SizeOf (SqIdxType));
  SqInfo^. SqiAlloc := shFileSize (SqInfo^. SqiFile) + 100;
  If SqInfo^. SqiAlloc > SqIdxArraySize Then SqInfo^. SqiAlloc := SqIdxArraySize;
  GetMem (SqIdx, SqInfo^. SqiAlloc * SizeOf (SqIdxType));
  If SqIdx = Nil Then SqInfo^. Error := 999 Else
  Begin
    shSeekFile (SqInfo^. SqiFile, 0);
    If shIOResult = 0 Then
    Begin
      If Not shRead (SqInfo^. SqiFile, SqIdx^, SqInfo^. SqiAlloc, NumRead)
      Then SqInfo^. Error := shIOResult;
    End Else
      SqInfo^. Error := 300;
  End;
End;

Procedure SqMsgObj. WriteIdx;
Begin
  If SqIdx = Nil Then SqInfo^. Error := 999 Else
  Begin
    shSeekFile (SqInfo^. SqiFile, 0);
    shTruncate (SqInfo^. SqiFile);

    If shIOResult = 0 Then
    Begin
      If Not shWrite (SqInfo^. SqiFile, SqIdx^, SqInfo^. SqBase. NumMsg)
      Then SqInfo^. Error := shIOResult;
    End Else
      SqInfo^. Error := 300;
  End;
End;

Procedure SqMsgObj. SeekFirst (MsgNum: LongInt);
Begin
  If SqInfo^. SqBase. NumMsg = 0 Then
  Begin
    SeekOver := True;
    Exit;
  End;

  SqInfo^. CurrIdx := 1;
  CurMsg := 1;
  ReadIdx;
  SeekOver := False;

  While SeekFound And (SqInfo^. CurrIdx < MsgNum) Do SeekNext;
End;

Function SqMsgObj. IdxHighest: LongInt;
Var
  i     : Word;
  Tmp   : LongInt;

Begin
  i := 1;
  Tmp := 0;

  While i <= SqInfo^. SqBase. NumMsg Do
  Begin
    If SqIdx^ [i]. UMsgId > Tmp Then Tmp := SqIdx^ [i]. UMsgId;
    Inc (i);
  End;

  IdxHighest := Tmp;
End;

Function SqMsgObj. GetMsgNum: LongInt;
Begin
  GetMsgNum := SqInfo^. CurrIdx;
End;

Function SqMsgObj. GetMsgNumRelative: LongInt;
Begin
  GetMsgNumRelative := CurMsg;
End;

Procedure SqMsgObj. SeekNext;
Begin
  SeekOver := CurMsg >= NumberOfMsgs;
  If Not SeekOver Then
  Begin
    Inc (CurMsg);
    SqInfo^. CurrIdx := Relative2Num (CurMsg);
  End;
End;

Procedure SqMsgObj. SeekPrior;
Begin
  SeekOver := CurMsg < 2;
  If Not SeekOver Then
  Begin
    Dec (CurMsg);
    SqInfo^. CurrIdx := Relative2Num (CurMsg);
  End;
End;

Function SqMsgObj. SeekFound: Boolean;
Begin
  SeekFound := (SqInfo^. CurrIdx >= 0) and Not SeekOver;
End;

Function SqMsgObj. GetIdxFramePos: LongInt;
Begin
  If SqInfo^. CurrIdx <= SqInfo^. SqBase. NumMsg
  Then GetIdxFramePos := SqIdx^ [SqInfo^. CurrIdx]. Ofs
  Else GetIdxFramePos := - 1;
End;

Function SqMsgObj. GetIdxHash: LongInt;
Begin
  If SqInfo^. CurrIdx <= SqInfo^. SqBase. NumMsg
  Then GetIdxHash := SqIdx^ [SqInfo^. CurrIdx]. Hash
  Else GetIdxHash := 0;
End;

Function SqMsgObj. IsLocal: Boolean; {Is current msg local}
Begin
  IsLocal := ((SqInfo^. MsgHdr. Attr And SqMsgLocal) <> 0);
End;

Function SqMsgObj. IsCrash: Boolean; {Is current msg crash}
Begin
  IsCrash := ((SqInfo^. MsgHdr. Attr And SqMsgCrash) <> 0);
End;

Function SqMsgObj. IsKillSent: Boolean; {Is current msg kill sent}
Begin
  IsKillSent := ((SqInfo^. MsgHdr. Attr And SqMsgKill) <> 0);
End;

Function SqMsgObj. IsSent: Boolean; {Is current msg sent}
Begin
  IsSent := ((SqInfo^. MsgHdr. Attr And SqMsgSent) <> 0);
End;

Function SqMsgObj. IsFAttach: Boolean; {Is current msg file attach}
Begin
  IsFAttach := ((SqInfo^. MsgHdr. Attr And SqMsgFile) <> 0);
End;

Function SqMsgObj. IsReqRct: Boolean; {Is current msg request receipt}
Begin
  IsReqRct := ((SqInfo^. MsgHdr. Attr And SqMsgRRQ) <> 0);
End;

Function SqMsgObj. IsReqAud: Boolean; {Is current msg request audit}
Begin
  IsReqAud := ((SqInfo^. MsgHdr. Attr And SqMsgArq) <> 0);
End;

Function SqMsgObj. IsRetRct: Boolean; {Is current msg a return receipt}
Begin
  IsRetRct := ((SqInfo^. MsgHdr. Attr And SqMsgCpt) <> 0);
End;

Function SqMsgObj. IsFileReq: Boolean; {Is current msg a file request}
Begin
  IsFileReq := ((SqInfo^. MsgHdr. Attr And SqMsgFreq) <> 0);
End;

Function SqMsgObj. IsRcvd: Boolean; {Is current msg received}
Begin
  IsRcvd := ((SqInfo^. MsgHdr. Attr And SqMsgRcvd) <> 0);
End;

Function SqMsgObj. IsPriv: Boolean; {Is current msg priviledged/private}
Begin
  IsPriv := ((SqInfo^. MsgHdr. Attr And SqMsgPriv) <> 0);
End;

Function SqMsgObj. IsEchoed: Boolean;
Begin
  IsEchoed := ((SqInfo^. MsgHdr. Attr And SqMsgScanned) = 0);
End;

Function SqMsgObj. IsDeleted: Boolean; {Is current msg deleted}
Begin
  IsDeleted := False;
End;

Function SqMsgObj. GetRefer: LongInt; {Get reply to of current msg}
Begin
  GetRefer := LongInt (SqInfo^. MsgHdr. ReplyTo);
End;

Procedure SqMsgObj. SetRefer (Num: LongInt); {Set reply to of current msg}
Begin
  SqInfo^. MsgHdr. ReplyTo := LongInt (Num);
End;

Var
  AlsoNum : LongInt;

Function SqMsgObj. GetSeeAlso: LongInt; {Get see also msg}
Begin
  GetSeeAlso := LongInt (SqInfo^. MsgHdr. Replies [1]);
  AlsoNum := 1;
End;

Function SqMsgObj. GetNextSeeAlso: LongInt; {Get see also msg}
Begin
  GetNextSeeAlso := LongInt (SqInfo^. MsgHdr. Replies [AlsoNum]);
  Inc (AlsoNum);
End;

Procedure SqMsgObj. SetSeeAlso (Num: LongInt); {Set see also msg}
Begin
  SqInfo^. MsgHdr. Replies [1] := LongInt (Num);
End;

Procedure SqMsgObj. SetAttr (St: Boolean; Mask: LongInt); {Set attribute}
Begin
  If St Then
    SqInfo^. MsgHdr. Attr := SqInfo^. MsgHdr. Attr Or Mask
  Else
    SqInfo^. MsgHdr. Attr := SqInfo^. MsgHdr. Attr And (Not Mask);
End;

Procedure SqMsgObj. SetLocal (St: Boolean); {Set local status}
Begin
  SetAttr (St, SqMsgLocal);
End;

Procedure SqMsgObj. SetRcvd (St: Boolean); {Set received status}
Begin
  SetAttr (St, SqMsgRcvd);
End;

Procedure SqMsgObj. SetPriv (St: Boolean); {Set priveledge vs public status}
Begin
  SetAttr (St, SqMsgPriv);
End;

Procedure SqMsgObj. SetEcho (ES: Boolean);
Begin
  SetAttr (Not ES, SqMsgScanned);
End;

Procedure SqMsgObj. SetCrash (St: Boolean); {Set crash netmail status}
Begin
  SetAttr (St, SqMsgCrash);
End;

Procedure SqMsgObj. SetKillSent (St: Boolean); {Set kill/sent netmail status}
Begin
  SetAttr (St, SqMsgKill);
End;

Procedure SqMsgObj. SetSent (St: Boolean); {Set sent netmail status}
Begin
  SetAttr (St, SqMsgSent);
End;

Procedure SqMsgObj. SetFAttach (St: Boolean); {Set file attach status}
Begin
  SetAttr (St, SqMsgFile);
End;

Procedure SqMsgObj. SetReqRct (St: Boolean); {Set request receipt status}
Begin
  SetAttr (St, SqMsgRrq);
End;

Procedure SqMsgObj. SetReqAud (St: Boolean); {Set request audit status}
Begin
  SetAttr (St, SqMsgarq);
End;

Procedure SqMsgObj. SetRetRct (St: Boolean); {Set return receipt status}
Begin
  SetAttr (St, SqMsgCpt);
End;

Procedure SqMsgObj. SetFileReq (St: Boolean); {Set file request status}
Begin
  SetAttr (St, SqMsgFreq);
End;

Procedure SqMsgObj. MsgStartUp;
Begin
  SqInfo^. CurrentFramePos := GetIdxFramePos;
  SqInfo^. CurrentUID := SqIdx^ [SqInfo^. CurrIdx].UMsgId;
  ReadFrame (SqInfo^. CurrentFramePos);
  ReadMsgHdr (SqInfo^. CurrentFramePos);
End;

Procedure SqMsgObj. MsgTxtStartUp;
Begin
  ReadText (SqInfo^. CurrentFramePos);
End;

Procedure SqMsgObj. SetMailType (MT: MsgMailType);
Begin
End;

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

Procedure SqMsgObj. ReWriteHdr;
Var
  AlreadyLocked          : Boolean;
  i                      : LongInt;

Begin
  i := 1;
  AlreadyLocked := SqInfo^. Locked;
  If Not AlreadyLocked Then LockMsgBase;

  WriteFrame (SqInfo^. CurrentFramePos);
  WriteMsgHdr (SqInfo^. CurrentFramePos);
  While ((i <= SqInfo^. SqBase. NumMsg) And (SqInfo^. CurrentFramePos <> SqIdx^ [i]. Ofs)) Do Inc (i);

  If SqIdx^ [i].Ofs = SqInfo^. CurrentFramePos Then
  If IsRcvd
  Then SqIdx^ [i]. Hash := 0
  Else SqIdx^ [i]. Hash := SqHashName (SqInfo^. MsgHdr. MsgTo);

  If Not AlreadyLocked Then UnLockMsgBase;
End;

Procedure SqMsgObj. DeleteMsg (CarePos: Boolean);
Var
  IsBegin, IsEnd        : Boolean;

Begin
  IsBegin := (CurMsg = 1);
  IsEnd   := (CurMsg = C^. Count);

  KillMsg (SqInfo^. CurrentUID);
  (*
  FreeMem (C^. At (CurMsg-1), 4);
  *)
  C^. AtDelete (CurMsg-1);

  If CarePos Then
  Begin
    If IsBegin Then SeekFirst (GetLowMsgNum) Else
    If IsEnd   Then SeekFirst (GetHighMsgNum) Else
    SeekFirst (Relative2Num (LongInt (C^. At (CurMsg-1))));
  End;
End;

Function SqMsgObj. GetLastRead (UNum: LongInt): LongInt;
Var
  LRec  : LongInt;

Begin
  If ((UNum + 1) * SizeOf (LRec)) > gFileSize (SqInfo^. FN + '.SQL') Then GetLastRead := 0 Else
  If LoadFilePos (SqInfo^. FN + '.SQL', LRec, SizeOf (LRec), UNum * SizeOf (LRec)) = 0
  Then GetLastRead := LRec
  Else GetLastRead := 0;
End;

Procedure SqMsgObj. SetLastRead (UNum: LongInt; LR: LongInt);
Var
  LRec    : LongInt;

Begin
  LRec := LR;

  If ((UNum + 1) * SizeOf (LRec)) > gFileSize (SqInfo^. FN + '.SQL')
  Then ExtendFile (SqInfo^. FN + '.SQL', (UNum + 1) * SizeOf (LRec));

  SaveFilePos (SqInfo^. FN + '.SQL', LRec, SizeOf (LRec), UNum * SizeOf (LRec));
End;

Function SqMsgObj. GetMsgLoc: LongInt;
Begin
  GetMsgLoc := GetMsgNum;
End;

Procedure SqMsgObj. SetMsgLoc (ML: LongInt);
Begin
  SeekFirst (ML);
End;

Procedure SqMsgObj. YoursFirst (Name: String; Handle: String);
Begin
  SqInfo^. CurrIdx := 0;
  ReadIdx;
  SqInfo^. SName := UpString (Name);
  SqInfo^. SHandle := UpString (Handle);
  SqInfo^. HName := SqHashName (Name);
  SqInfo^. HHandle := SqHashName (Handle);
  YoursNext;
End;

Procedure SqMsgObj. YoursNext;
Var
  WasFound: Boolean;

Begin
  WasFound := False;
  Inc (SqInfo^. CurrIdx);

  While ((SqInfo^. CurrIdx <= SqInfo^. SqBase. NumMsg) And (Not WasFound)) Do
  Begin
    While ((SqIdx^ [SqInfo^. CurrIdx].Hash <> SqInfo^. HName) And
           (SqIdx^ [SqInfo^. CurrIdx].Hash <> SqInfo^. HHandle) And
           (SqInfo^. CurrIdx <= SqInfo^. SqBase. NumMsg))
    Do Inc (SqInfo^. CurrIdx);

    If SqInfo^. CurrIdx <= SqInfo^. SqBase. NumMsg Then
    Begin
      MsgStartUp;

      If ((Not IsRcvd) And ((UpString (GetTo) = SqInfo^. SName) Or
          (UpString (GetTo) = SqInfo^. SHandle)))
      Then WasFound := True
      Else Inc (SqInfo^. CurrIdx);
    End;

  End;
End;

Function SqMsgObj. YoursFound: Boolean;
Begin
  YoursFound := SqInfo^. CurrIdx <= SqInfo^. SqBase. NumMsg;
End;

Function SqMsgObj. GetMsgDisplayNum: LongInt;
Begin
  GetMsgDisplayNum := SqInfo^. CurrIdx;
End;

Function SqMsgObj. GetTxtPos: LongInt;
Begin
  GetTxtPos := SqInfo^. TxtCtr;
End;

Procedure SqMsgObj. SetTxtPos (TP: LongInt);
Begin
  SqInfo^. TxtCtr := TP;
End;

Procedure SqMsgObj. GetTotalMsgs;
Var
  cNum  : LongInt;

Begin
  If SqInfo^. SqBase. NumMsg = 0 Then Exit;

  cNum := SqInfo^. CurrIdx;
  SeekFirst (1);

  While SeekFound Do
  Begin
    C^. Insert (Pointer (CurMsg));
    SeekNext;
  End;

  SqInfo^. CurrIdx := cNum;
  MsgStartUp;
End;

Function SqMsgObj. NumberOfMsgs: LongInt;
Begin
  NumberOfMsgs := SqInfo^. SqBase. NumMsg;
End;

End.