Unit MKMsgHud;        {Hudson/QuickBbs-style Message Base}

{$I-}
{$IFNDEF OS2}
{$V-,S-,F+,R-,X+,O+}
{$ENDIF}

Interface

Uses
  MKMsgAbs,
  MKGlobT,
  tMisc,
  MkFile,
  Objects,
  tGlob;

Type
{$IFDEF WIN32}
  Integer = SmallInt;
{$ENDIF}

  MsgTxtType = String [255];           {MsgTxt.Bbs file}
  MsgToIdxType = String [35];          {MsgToIdx.Bbs file}

  MsgInfoType = Record                 {MsgInfo.Bbs file}
    LowMsg: Word;                      {Low message number in file}
    HighMsg: Word;                     {High message number in file}
    Active: Word;                      {Number of active messages}
    AreaActive: Array [1..200] of Word;{Number active in each area}
  End;

  MsgIdxType = Record                  {MsgIdx.Bbs file}
    MsgNum      : Word;                {Message number}
    Area        : Byte;                {Message area}
  End;

  MsgHdrType = Record                  {MsgHdr.Bbs file}
    MsgNum: Word;                      {Message number}
    ReplyTo: Word;                     {Message is reply to this number}
    SeeAlso: Word;                     {Message has replies}
    Extra: Word;                       {No longer used}
    StartRec: Word;                    {starting seek offset in MsgTxt.Bbs}
    NumRecs: Word;                     {number of MsgTxt.Bbs records}
    DestNet: Integer;                  {NetMail Destination Net}
    DestNode: Integer;                 {NetMail Destination Node}
    OrigNet: Integer;                  {NetMail Originating Net}
    OrigNode: Integer;                 {NetMail Originating Node}
    DestZone: Byte;                    {NetMail Destination Zone}
    OrigZone: Byte;                    {NetMail Originating Zone}
    Cost: Word;                        {NetMail Cost}
    MsgAttr: Byte;                     {Message attribute - see constants}
    NetAttr: Byte;                     {Netmail attribute - see constants}
    Area: Byte;                        {Message area}
    Time    : String [5];              {Message time in HH:MM}
    Date    : String [8];              {Message date in MM-DD-YY}
    MsgTo,                             {Message is intended for}
    MsgFrom : String [35];             {Message was written by}
    Subj    : String [72];             {Message subject}
  End;

  LastReadType = Array [1..200] Of Word; {LASTREAD.BBS file}

Const                                  {MsgHdr.MsgAttr}
  maDeleted =       1;                 {Message is deleted}
  maUnmovedNet =    2;                 {Unexported Netmail message}
  maNetMail =       4;                 {Message is netmail message}
  maPriv =          8;                 {Message is private}
  maRcvd =         16;                 {Message is received}
  maUnmovedEcho =  32;                 {Unexported Echomail message}
  maLocal =        64;                 {"Locally" entered message}

Const                                  {MsgHdr.NetAttr}
  naKillSent =      1;                 {Delete after exporting}
  naSent =          2;                 {Msg has been sent}
  naFAttach =       4;                 {Msg has file attached}
  naCrash =         8;                 {Msg is crash}
  naReqRcpt =      16;                 {Msg requests receipt}
  naReqAudit =     32;                 {Msg requests audit}
  naRetRcpt =      64;                 {Msg is a return receipt}
  naFileReq =     128;                 {Msg is a file request}

  TxtSize  = 170;
  SeekSize = 250;
  YourSize = 100;

  HudsonLast: String = '';
  HudsonEcho: String = '';

Type
  TxtRecsType = Array [1..TxtSize] Of MsgTxtType;
  SeekArrayType = Array [1..SeekSize] Of MsgIdxType;

  YourSearchType = Record
    NumRead                 : SysInt;
    SeekStart, CurrPos      : Integer;
    MsgFound                : Boolean;
    Name, Handle            : String [35];
    SearchArray             : Array [1..YourSize] Of String [35];
  End;

  HudsonMsgType = Record
    MsgPath: String [50];                {Message base directory}
    MsgInfoFile, MsgTxtFile, MsgHdrFile,
    MsgToIdxFile, MsgIdxFile : shFile;
    Opened, Locked           : Boolean;
    Error                    : Word;     {0=no error}
    MsgHdr  : MsgHdrType;                {Current message header}
    MsgInfo : MsgInfoType;               {MsgInfo record}
    MsgPos: Word;                        {MsgHdr seek position of current rec}
    SeekNumRead: SysInt;                 {Number of records in the array}
    SeekPos: Integer;                    {Current position in array}
    SeekStart: Word;                     {File Pos of 1st record in Idx Array}
    SeekOver: Boolean;                   {More idx records?}
    CurrMsgNum,                          {Current Seek Msg number}
    CurrTxtRec,                          {Current txtrec in current msg}
    CurrTxtPos: Word;                    {Current position in current txtrec}
    EOM: Boolean;                        {end of message text}
    OrigPoint,                           {Point Addr orig}
    DestPoint: Word;                     {Point Addr destination}
    Echo,                                {Should message be exported}
    CRLast: Boolean;                     {Last char was CR #13}
    Area: Word;
    MT: MsgMailType;
  End;

  HudsonMsgObj = Object (AbsMsgObj)  {Message Export Object}
    MsgRec                        : ^HudsonMsgType;
    MsgChars                      : ^TxtRecsType;
    SeekArray                     : ^SeekArrayType;
    YourInfo                      : ^YourSearchType;
    FoundNext                     : Boolean;

    Constructor Init; {Initialize}
    Destructor Done; Virtual; {Done}
    Procedure MsgStartUp; Virtual; {Setup message/read header}
    Procedure MsgTxtStartUp; Virtual; {Setup message text}
    Function  EOM: Boolean; Virtual; {No more msg text}
    Function  GetChar: Char; Virtual; {Get msg text character}
    Function  NextChar (Var Rec: Word; Var PPos: Word): Boolean; {internal to position for char}
    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; {Find prior matching msg}
    Procedure SeekRead (NumToRead: Word); {Refill seek array}
    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; {Is current msg unmoved echomail msg}
    Function  IsUns: Boolean; Virtual; {Is current msg unsent}
    Procedure YoursFirst (Name: String; Handle: String); Virtual; {Search for mail to caller}
    Procedure YoursNext; Virtual; {Search for next message}
    Function  YoursFound: Boolean; Virtual; {Found a message}
    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}
    Procedure SetDate (SDate: String); Virtual; {Set message date}
    Procedure SetTime (STime: String); Virtual; {Set message time}
    Procedure SetEcho (ES: Boolean); Virtual; {Set echo status}
    Procedure SetMsgAttr (Setting: Boolean; Mask: Word);
    Procedure SetNetAttr (Setting: Boolean; Mask: Word);
    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 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 message base}
    Function  ReWriteMsg: Word; Virtual; {ReWrite msg in message base}
    Function  OpenMsgBase: Word; Virtual; {Individual msg open}
    Function  CloseMsgBase: Word; Virtual; {Individual msg close}
    Function  SeekEnd: Word; Virtual; {Seek to eof for msg base files}
    Function  SeekMsgBasePos (Position: Word): Word; Virtual; {Seek to pos of Msg Base File}
    Function  Check: Word; Virtual; {Check if msg base is ok}
    Function  CreateMsgBase (MaxMsg: Word; MaxDays: Word): Word; Virtual; {Create initial msg base files}
    Function  LockMsgBase: Boolean; Virtual; {Lock msg base for updating}
    Function  UnlockMsgBase: Boolean; Virtual; {Unlock msg base after updating}
    Function  WriteMailIdx (FN: String; MsgPos: Word): Word; Virtual; {Write Netmail or EchoMail.Bbs}
    Function  MsgBaseSize: Word; Virtual; {Number of msg base index records}
    Function  GetNumActive: Word; Virtual; {Get number of active messages}
    Procedure StartNewMsg; Virtual; {Initialize message}
    Procedure SetMsgPath (MP: String); Virtual;
    Function  SeekFound: Boolean; Virtual; {Seek msg found}
    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  GetMsgLoc: LongInt; Virtual; {To allow reseeking to message}
    Procedure SetMsgLoc (ML: LongInt); Virtual; {Reseek to message}
    Function  GetLastRead (UNum: LongInt): LongInt; Virtual; {Get last read for user num}
    Procedure SetLastRead (UNum: LongInt; LR: LongInt); Virtual; {Set last read}
    Procedure GetAllLastRead (UNum: LongInt; Var LR: LastReadType); Virtual; {all areas}
    Procedure GetHighest (Var LR: LastReadType); Virtual; {Get highest all areas}
    Function  GetTxtPos: LongInt; Virtual;
    Procedure SetTxtPos (TP: LongInt); Virtual;
    Function  MsgBaseExists: Boolean; Virtual;
    Procedure GetTotalMsgs; Virtual;
    Function  GetMsgNumRelative: LongInt; Virtual;
    Function OpenAll (Mode: Word; CloseBefore: Boolean): Word;
  End;

  HudsonMsgPtr = ^HudsonMsgObj;

Implementation

Constructor HudsonMsgObj. Init;
Begin
  New (MsgRec);
  New (MsgChars);
  New (SeekArray);
  New (YourInfo);

  If ((MsgRec = Nil) Or (MsgChars = Nil) Or (SeekArray = Nil) Or (YourInfo = Nil)) Then
  Begin
    If MsgRec <> Nil Then Dispose (MsgRec);
    If MsgChars <> Nil Then Dispose (MsgChars);
    If SeekArray <> Nil Then Dispose (SeekArray);
    If YourInfo <> Nil Then Dispose (YourInfo);
    Fail; Exit;
  End;

  FillChar (MsgRec^, SizeOf (MsgRec^), #0);
  FillChar (MsgChars^, SizeOf (MsgChars^), #0);
  FillChar (SeekArray^, SizeOf (SeekArray^), #0);
  FillChar (YourInfo^, SizeOf (YourInfo^), #0);
  C := New (PCollection, Init (30, 5));
  CurMsg := 1;
End;

Procedure HudsonMsgObj. YoursFirst (Name: String; Handle: String);
Begin
  YourInfo^. NumRead := 0;
  YourInfo^. SeekStart := 0;
  YourInfo^. CurrPos := 1;
  YourInfo^. MsgFound := False;
  YourInfo^. Name := Copy (Trim (UpString (Name)), 1, 35);
  YourInfo^. Handle := Copy (Trim (UpString (Handle)), 1, 35);
  MsgRec^. SeekOver := False;
  YoursNext;
End;

Procedure HudsonMsgObj. YoursNext;
Var
  SearchOver: Boolean;

Begin
  Inc (YourInfo^. CurrPos);
  SearchOver := False;
  YourInfo^. MsgFound := False;

  While Not SearchOver Do
  Begin
    If YourInfo^. CurrPos > YourInfo^. NumRead Then
    Begin
      Inc (YourInfo^. SeekStart, YourInfo^. NumRead);
      YourInfo^. CurrPos := 1;
      shSeekFile (MsgRec^. MsgToIdxFile, YourInfo^. SeekStart);

      If shIOResult <> 0 Then YourInfo^. NumRead := 0;
      If Not shRead (MsgRec^. MsgToIdxFile, YourInfo^. SearchArray, YourSize, YourInfo^. NumRead) Then
      Begin
        MsgRec^. Error := 1000;
        YourInfo^. NumRead := 0;
      End;
    End;

    If YourInfo^. NumRead = 0 Then SearchOver := True Else
    If (((UpString (YourInfo^. SearchArray [YourInfo^. CurrPos] ) = YourInfo^. Name) Or
       (UpString (YourInfo^. SearchArray [YourInfo^. CurrPos] ) = YourInfo^. Handle)) And
       ((YourInfo^. CurrPos > 0) And (YourInfo^. CurrPos <= YourInfo^. NumRead))) Then
    Begin
      MsgRec^. MsgPos := YourInfo^. SeekStart + YourInfo^. CurrPos - 1;
      MsgStartUp;

      If Not IsRcvd And (MsgRec^. MsgHdr. Area = MsgRec^. Area) Then
      Begin
        YourInfo^. MsgFound := True;
        SearchOver := True;
      End;
    End;

    If Not YourInfo^. MsgFound Then Inc (YourInfo^. CurrPos);
  End;
End;

Function  HudsonMsgObj. YoursFound: Boolean;
Begin
  YoursFound := YourInfo^. MsgFound;
End;

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

Destructor HudsonMsgObj. Done;
Begin
  If C <> Nil Then
  Begin

    While C^. Count > 0 Do
    Begin
      { FreeMem (C^. At (0), 4); }
      C^. AtDelete (0);
    End;

    Dispose (C, Done);
  End;

  Dispose (MsgRec);
  Dispose (MsgChars);
  Dispose (SeekArray);
  Dispose (YourInfo);
End;

Procedure HudsonMsgObj. MsgStartUp;
Var
  NumRead: SysInt;

Begin
  MsgRec^. Error := SeekMsgBasePos (MsgRec^. MsgPos);
  MsgRec^. OrigPoint := 0;
  MsgRec^. DestPoint := 0;

  If MsgRec^. Error = 0 Then
  If Not shRead (MsgRec^. MsgHdrFile, MsgRec^. MsgHdr, 1, NumRead)
  Then MsgRec^. Error := shIOResult;
End;

Procedure HudsonMsgObj. SetMsgAttr (Setting: Boolean; Mask: Word);
Begin
  If Setting Then
    MsgRec^. MsgHdr. MsgAttr := MsgRec^. MsgHdr. MsgAttr Or Mask
  Else
    MsgRec^. MsgHdr. MsgAttr := MsgRec^. MsgHdr. MsgAttr And (Not Mask);
End;

Procedure HudsonMsgObj. SetRcvd (RS: Boolean);
Begin
  SetMsgAttr (RS, maRcvd);
End;

Procedure HudsonMsgObj. SetPriv (PS: Boolean);
Begin
  SetMsgAttr (PS, maPriv);
End;

Procedure HudsonMsgObj. SetNetAttr (Setting: Boolean; Mask: Word);
Begin
  If Setting
  Then
    MsgRec^. MsgHdr. NetAttr := MsgRec^. MsgHdr. NetAttr Or Mask
  Else
    MsgRec^. MsgHdr. NetAttr := MsgRec^. MsgHdr. NetAttr And (Not Mask);
End;

Procedure HudsonMsgObj. SetSeeAlso (SAlso: LongInt);
Begin
  MsgRec^. MsgHdr. SeeAlso := SAlso;
End;

Procedure HudsonMsgObj. SetFrom (Name: String); {Set msg from}
Begin
  MsgRec^. MsgHdr. MsgFrom := Name;
End;

Procedure HudsonMsgObj. SetTo (Name: String); {Set msg to}
Begin
  MsgRec^. MsgHdr. MsgTo := Name;
End;

Procedure HudsonMsgObj. SetSubj (Str: String); {Set msg subject}
Begin
  MsgRec^. MsgHdr. Subj := Str;
End;

Function HudsonMsgObj. GetFrom: String;
Begin
  GetFrom := Trim (MsgRec^. MsgHdr. MsgFrom);
End;

Function HudsonMsgObj. GetTo: String;
Begin
  GetTo := Trim (MsgRec^. MsgHdr. MsgTo);
End;

Function HudsonMsgObj. GetSubj: String;
Begin
  GetSubj := Trim (MsgRec^. MsgHdr. Subj);
End;

Function HudsonMsgObj. GetCost: Word;
Begin
  GetCost := MsgRec^. MsgHdr. Cost;
End;

Function HudsonMsgObj. GetDate: String;            {Get date of current msg}
Begin
  GetDate := MsgRec^. MsgHdr. Date;
End;

Function HudsonMsgObj. GetTime: String;            {Get time of current msg}
Begin
  With MsgRec^. MsgHdr Do
  Begin
    Time := LeftPadCh (Trim (Copy (Time, 1, 2)), '0', 2) + ':' + LeftPadCh (Trim (Copy (Time, 4, 2)), '0', 2);
    GetTime := Time;
  End;
End;

Function HudsonMsgObj. GetRefer: LongInt;
Begin
  GetRefer := MsgRec^. MsgHdr. ReplyTo;
End;

Function HudsonMsgObj. GetSeeAlso: LongInt;
Begin
  GetSeeAlso := MsgRec^. MsgHdr. SeeAlso;
End;

Function HudsonMsgObj. GetMsgNum: LongInt;
Begin
  GetMsgNum := MsgRec^. MsgHdr. MsgNum;
End;

Procedure HudsonMsgObj. GetOrig (Var Addr: AddrType);
Begin
  Addr. Zone := MsgRec^. MsgHdr. OrigZone;
  Addr. Net := MsgRec^. MsgHdr. OrigNet;
  Addr. Node := MsgRec^. MsgHdr. OrigNode;
  Addr. Point := MsgRec^. OrigPoint;
End;

Procedure HudsonMsgObj. GetDest (Var Addr: AddrType);
Begin
  Addr. Zone := MsgRec^. MsgHdr. DestZone;
  Addr. Net := MsgRec^. MsgHdr. DestNet;
  Addr. Node := MsgRec^. MsgHdr. DestNode;
  Addr. Point := MsgRec^. DestPoint;
End;

Function HudsonMsgObj. IsLocal: Boolean;
Begin
  IsLocal := ((MsgRec^. MsgHdr. MsgAttr And maLocal) <> 0);
End;

Function HudsonMsgObj. IsCrash: Boolean;
Begin
  IsCrash := ((MsgRec^. MsgHdr. NetAttr And naCrash) <> 0);
End;

Function HudsonMsgObj. IsKillSent: Boolean;
Begin
  IsKillSent := ((MsgRec^. MsgHdr. NetAttr And naKillSent) <> 0);
End;

Function HudsonMsgObj. IsSent: Boolean;
Begin
  IsSent := ((MsgRec^. MsgHdr. NetAttr And naSent) <> 0);
End;

Function HudsonMsgObj. IsFAttach: Boolean;
Begin
  IsFAttach := ((MsgRec^. MsgHdr. NetAttr And naFAttach) <> 0);
End;

Function HudsonMsgObj. IsReqRct: Boolean;
Begin
  IsReqRct := ((MsgRec^. MsgHdr. NetAttr And naReqRcpt) <> 0);
End;

Function HudsonMsgObj. IsReqAud: Boolean;
Begin
  IsReqAud := ((MsgRec^. MsgHdr. NetAttr And naReqAudit) <> 0);
End;

Function HudsonMsgObj. IsRetRct: Boolean;
Begin
  IsRetRct := ((MsgRec^. MsgHdr. NetAttr And naRetRcpt) <> 0);
End;

Function HudsonMsgObj. IsFileReq: Boolean;
Begin
  IsFileReq := ((MsgRec^. MsgHdr. NetAttr And naFileReq) <> 0);
End;

Function HudsonMsgObj. IsRcvd: Boolean;
Begin
  IsRcvd := ((MsgRec^. MsgHdr. MsgAttr And maRcvd) <> 0);
End;

Function HudsonMsgObj. IsPriv: Boolean;
Begin
  IsPriv := ((MsgRec^. MsgHdr. MsgAttr And maPriv) <> 0);
End;

Function HudsonMsgObj. IsDeleted: Boolean;
Begin
  IsDeleted := ((MsgRec^. MsgHdr. MsgAttr And maDeleted) <> 0);
End;

Function HudsonMsgObj. IsEchoed: Boolean;
Begin
  IsEchoed := MsgRec^. Echo;
    {  IsEchoed := ((MsgRec^.MsgHdr.MsgAttr and maUnmovedEcho) <> 0); }
    {  IsUnmovedNet := ((MsgRec^.MsgHdr.MsgAttr and maUnmovedNet) <> 0);}
End;

Procedure HudsonMsgObj. MsgTxtStartUp;
Var
  NumRead    : SysInt;
  MaxTxt     : Word;

Begin
  LastSoft := False;
  If MsgRec^. MsgHdr. NumRecs > TxtSize
  Then MaxTxt := TxtSize
  Else MaxTxt := MsgRec^. MsgHdr. NumRecs;

  shSeekFile (MsgRec^. MsgTxtFile, MsgRec^. MsgHdr. StartRec);
  If shIOResult <> 0 Then MsgRec^. Error := 2222;
  If Not shRead (MsgRec^. MsgTxtFile, MsgChars^, MaxTxt, NumRead) Then MsgRec^. Error := shIOResult;
  If NumRead <> MaxTxt Then MsgRec^. Error := 1111;

  MsgRec^. CurrTxtRec := 1;
  MsgRec^. CurrTxtPos := 1;
  MsgRec^. EOM := False;
End;

Function HudsonMsgObj. NextChar (Var Rec: Word; Var PPos: Word): Boolean;
Var
  MoreNext      : Boolean;

Begin
  MoreNext := True;
  NextChar := True;

  While MoreNext Do
  If ((Rec > MsgRec^. MsgHdr. NumRecs) Or (Rec > TxtSize)) Then MoreNext := False Else
  Begin
    If (PPos > Length (MsgChars^ [Rec] )) Then
    Begin
      Inc (Rec);
      PPos := 1;
    End Else
      MoreNext := False;
  End;

  If ((Rec > MsgRec^. MsgHdr. NumRecs) Or (Rec > TxtSize)) Then NextChar := False;
End;


Function HudsonMsgObj. GetChar: Char;
Var
  MoreNext: Boolean;

Begin
  MoreNext := True;
  If ((MsgRec^. CurrTxtRec <= MsgRec^. MsgHdr. NumRecs) And
     (MsgRec^. CurrTxtRec <= TxtSize)) Then
  Begin
    While MoreNext Do
    Begin
      If ((MsgRec^. CurrTxtRec > MsgRec^. MsgHdr. NumRecs) Or
          (MsgRec^. CurrTxtRec > TxtSize))
      Then MoreNext := False Else
      Begin
        If (MsgRec^. CurrTxtPos > Length (MsgChars^ [MsgRec^. CurrTxtRec] )) Then
        Begin
          Inc (MsgRec^. CurrTxtRec);
          MsgRec^. CurrTxtPos := 1;
        End Else
          MoreNext := False;
      End;
    End;

    If ((MsgRec^. CurrTxtRec > MsgRec^. MsgHdr. NumRecs) Or
        (MsgRec^. CurrTxtRec > TxtSize)) Then
      MsgRec^. EOM := True;
  End Else
    MsgRec^. EOM := True;

  If MsgRec^. EOM
  Then
    GetChar := #0
  Else
    GetChar := MsgChars^ [MsgRec^. CurrTxtRec] [MsgRec^. CurrTxtPos];

  Inc (MsgRec^. CurrTxtPos);
End;

Function HudsonMsgObj. EOM: Boolean;
Begin
  EOM := MsgRec^. EOM;
End;

Procedure HudsonMsgObj. StartNewMsg;  {Initialize message}
Var
  oNum  : LongInt;

Const
  Blank = '* Blank *';

Begin
  Inherited StartNewMsg;
  MsgRec^. CurrTxtRec := 1;
  MsgRec^. CurrTxtPos := 0;
  oNum := MsgRec^. MsgHdr. MsgNum;
  FillChar (MsgRec^. MsgHdr, SizeOf (MsgRec^. MsgHdr), #0);
  MsgRec^. MsgHdr. MsgNum := oNum;
  MsgRec^. Echo := False;
  MsgRec^. MsgHdr. Time := '00:00';
  MsgRec^. MsgHdr. Date := '00-00-00';
  MsgRec^. MsgHdr. MsgTo := Blank;
  MsgRec^. MsgHdr. MsgFrom := Blank;
  MsgRec^. MsgHdr. Subj := Blank;
  MsgRec^. CRLast := True;
End;

Procedure HudsonMsgObj. SetEcho (ES: Boolean); {Set echo status}
Begin
  MsgRec^. Echo := ES;
End;

Procedure HudsonMsgObj. SetLocal (LS: Boolean); {Set local status}
Begin
  If LS Then
    MsgRec^. MsgHdr. MsgAttr := MsgRec^. MsgHdr. MsgAttr Or maLocal
  Else
    MsgRec^. MsgHdr. MsgAttr := MsgRec^. MsgHdr. Msgattr And (Not maLocal);
End;

Procedure HudsonMsgObj. SetCrash (SS: Boolean); {Set crash netmail status}
Begin
  If SS Then
    MsgRec^. MsgHdr. NetAttr := MsgRec^. MsgHdr. NetAttr Or naCrash
  Else
    MsgRec^. MsgHdr. NetAttr := MsgRec^. MsgHdr. NetAttr And (Not naCrash);
End;

Procedure HudsonMsgObj. SetKillSent (SS: Boolean); {Set kill/sent netmail status}
Begin
  If SS Then
    MsgRec^. MsgHdr. NetAttr := MsgRec^. MsgHdr. NetAttr Or naKillSent
  Else
    MsgRec^. MsgHdr. NetAttr := MsgRec^. MsgHdr. NetAttr And (Not naKillSent);
End;

Procedure HudsonMsgObj. SetSent (SS: Boolean); {Set sent netmail status}
Begin
  If SS Then
    MsgRec^. MsgHdr. NetAttr := MsgRec^. MsgHdr. NetAttr Or naSent
  Else
    MsgRec^. MsgHdr. NetAttr := MsgRec^. MsgHdr. NetAttr And (Not naSent);
End;

Procedure HudsonMsgObj. SetFAttach (SS: Boolean); {Set file attach status}
Begin
  If SS
  Then MsgRec^. MsgHdr. NetAttr := MsgRec^. MsgHdr. NetAttr Or naFAttach
  Else MsgRec^. MsgHdr. NetAttr := MsgRec^. MsgHdr. NetAttr And (Not naFAttach);
End;

Procedure HudsonMsgObj. SetReqRct (SS: Boolean); {Set request receipt status}
Begin
  If SS Then
    MsgRec^. MsgHdr. NetAttr := MsgRec^. MsgHdr. NetAttr Or naReqRcpt
  Else
    MsgRec^. MsgHdr. NetAttr := MsgRec^. MsgHdr. NetAttr And (Not naReqRcpt);
End;

Procedure HudsonMsgObj. SetReqAud (SS: Boolean); {Set request audit status}
Begin
  If SS Then
    MsgRec^. MsgHdr. NetAttr := MsgRec^. MsgHdr. NetAttr Or naReqAudit
  Else
    MsgRec^. MsgHdr. NetAttr := MsgRec^. MsgHdr. NetAttr And (Not naReqAudit);
End;

Procedure HudsonMsgObj. SetRetRct (SS: Boolean); {Set return receipt status}
Begin
  If SS Then
    MsgRec^. MsgHdr. NetAttr := MsgRec^. MsgHdr. NetAttr Or naRetRcpt
  Else
    MsgRec^. MsgHdr. NetAttr := MsgRec^. MsgHdr. NetAttr And (Not naRetRcpt);
End;

Procedure HudsonMsgObj. SetFileReq (SS: Boolean); {Set file request status}
Begin
  If SS Then
    MsgRec^. MsgHdr. NetAttr := MsgRec^. MsgHdr. NetAttr Or naFileReq
  Else
    MsgRec^. MsgHdr. NetAttr := MsgRec^. MsgHdr. NetAttr And (Not naFileReq);
End;

Procedure HudsonMsgObj. SetCost (SCost: Word);      {Set message cost}
Begin
  MsgRec^. MsgHdr. Cost := SCost;
End;

Procedure HudsonMsgObj. SetRefer (SRefer: LongInt);    {Set message reference}
Begin
  MsgRec^. MsgHdr. ReplyTo := SRefer;
End;

Procedure HudsonMsgObj. SetDate (SDate: String);    {Set message date}
Begin
  MsgRec^. MsgHdr. Date := Copy (LeftPadCh (SDate, '0', 8), 1, 8);
  MsgRec^. MsgHdr. Date [3] := '-';
  MsgRec^. MsgHdr. Date [6] := '-';
End;

Procedure HudsonMsgObj. SetTime (STime: String);    {Set message time}
Begin
  MsgRec^. MsgHdr. Time := Copy (LeftPadCh (STime, '0', 5), 1, 5);
  MsgRec^. MsgHdr. Time [3] := ':';
End;

Procedure HudsonMsgObj. 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 HudsonMsgObj. DoChar (CH: Char);          {Add character to message text}
Begin
  If (MsgRec^. CurrTxtRec < TxtSize) Or (MsgRec^. CurrTxtPos < 255) Then
  Begin
    If MsgRec^. CurrTxtPos = 255 Then
    Begin
      MsgChars^ [MsgRec^. CurrTxtRec] [0] := Chr (255);
      Inc (MsgRec^. CurrTxtRec);
      MsgRec^. CurrTxtPos := 0;
    End;

    Case CH Of
      #$0D: MsgRec^. CRLast := True;
      #$0A:;
      #$8D:;
    Else
      MsgRec^. CRLast := False;
    End;

    Inc (MsgRec^. CurrTxtPos);
    MsgChars^ [MsgRec^. CurrTxtRec] [MsgRec^. CurrTxtPos] := CH;
  End;
End;

Procedure HudsonMsgObj. DoStringLn (Str: String);   {Add string and newline to msg text}
Begin
  DoString (Str);
  DoChar (#13);
End;

Function HudsonMsgObj. WriteMsg: Word;
Var
  WriteError, MsgPos    : Word;
  MsgIdx                : MsgIdxType;
  FN                    : String [13];
  AlreadyLocked         : Boolean;
  oPos, oNum            : LongInt;
  S                     : String;

Begin
  If aHld or aDir or aImm or aLock Then
  Begin
    S := '';
    If aHld Then S := S + 'HLD ';
    If aDir Then S := S + 'DIR ';
    If aImm Then S := S + 'IMM ';
    If aLock Then S := S + 'LOK ';
    S := Trim (S);
    DoStringLn ('');
    DoKludgeLn (#1'FLAGS ' + S);
  End;

  oNum := MsgRec^. MsgHdr. MsgNum;
  oPos := MsgRec^. MsgPos;
  If shFileSize (MsgRec^. MsgTxtFile) > $ff00 Then WriteError := 99 Else WriteError := 0;
  If Not MsgRec^. CRLast Then DoChar (#$0D);
  MsgRec^. MsgHdr. NumRecs := MsgRec^. CurrTxtRec;
  MsgChars^ [MsgRec^. CurrTxtRec] [0] := Chr (MsgRec^. CurrTxtPos);

  Case MsgRec^. MT Of

    mmtNormal   : Begin
                    MsgRec^. MsgHdr. MsgAttr := MsgRec^. MsgHdr. MsgAttr And
                    Not (maNetMail + maUnmovedNet + maUnmovedEcho);
                  End;

    mmtEchoMail : Begin
                    MsgRec^. MsgHdr. MsgAttr := MsgRec^. MsgHdr. MsgAttr And
                    Not (maNetMail + maUnmovedNet);
                    {If MsgRec^. Echo Then} MsgRec^. MsgHdr. MsgAttr := MsgRec^. MsgHdr. MsgAttr Or maUnmovedEcho;
                  End;

    mmtNetMail  : Begin
                    MsgRec^. MsgHdr. MsgAttr := MsgRec^. MsgHdr. MsgAttr And Not (maUnmovedEcho);
                    MsgRec^. MsgHdr. MsgAttr := MsgRec^. MsgHdr. MsgAttr Or maNetMail;
                    {If MsgRec^. Echo Then} MsgRec^. MsgHdr. MsgAttr := MsgRec^. MsgHdr. MsgAttr Or maUnmovedNet;
                  End;
  End;

  MsgRec^. MsgHdr. Area := MsgRec^. Area;
  AlreadyLocked := MsgRec^. Locked;

  If Not AlreadyLocked Then
  If Not LockMsgBase Then WriteError := 5;

  If WriteError = 0 Then WriteError := OpenAll (fmReadWrite, True);

  If WriteError = 0 Then WriteError := SeekEnd;
  If WriteError = 0 Then               {Write MsgHdr}
  Begin
    MsgRec^. MsgHdr. StartRec := shFileSize (MsgRec^. MsgTxtFile);
    MsgPos := shFileSize (MsgRec^. MsgHdrFile);
    Inc (MsgRec^. MsgInfo. HighMsg);
    MsgRec^. MsgHdr. MsgNum := MsgRec^. MsgInfo. HighMsg;
    Inc (MsgRec^. MsgInfo. Active);
    Inc (MsgRec^. MsgInfo. AreaActive [MsgRec^. MsgHdr. Area] );
    If Not shWrite (MsgRec^. MsgHdrFile, MsgRec^. MsgHdr, 1)
    Then WriteError := shIOResult;
  End;

  If WriteError = 0 Then
  If Not shWrite (MsgRec^. MsgToIdxFile, MsgRec^. MsgHdr. MsgTo, 1) Then WriteError := shIOResult;

  If WriteError = 0 Then               {Write MsgIdx}
  Begin
    MsgIdx. MsgNum := MsgRec^. MsgHdr. MsgNum;
    MsgIdx. Area := MsgRec^. MsgHdr. Area;
    If Not shWrite (MsgRec^. MsgIdxFile, MsgIdx, 1) Then WriteError := shIOResult;
  End;

  If WriteError = 0 Then               {Write MsgTxt}
  If Not shWrite (MsgRec^. MsgTxtFile, MsgChars^, MsgRec^. MsgHdr. NumRecs) Then WriteError := shIOResult;

  If WriteError = 0 Then
  Begin
    Case MsgRec^. MT Of
      mmtEchoMail : FN := 'ECHOMAIL.BBS';
      mmtNetMail  : FN := 'NETMAIL.BBS';
    Else
      FN := '';
    End; {Case MsgType}

    If Length (FN) > 0 Then WriteError := WriteMailIdx (FN, MsgPos);
  End;

  If WriteError = 0 Then
  If Not AlreadyLocked Then
  If Not UnlockMsgBase Then WriteError := 5;

  WriteError := OpenAll (fmReadOnly, True);

  MsgRec^. MsgPos := MsgPos;
  WriteMsg := WriteError;
  MsgRec^. SeekOver := False;

  C^. Insert (Pointer (MsgRec^. MsgHdr. MsgNum));

  MsgRec^. MsgHdr. MsgNum := oNum;
  MsgRec^. MsgPos := oPos;
  MsgStartUp;
End;

Function HudsonMsgObj. ReWriteMsg: Word;
Var
  WriteError                  : Word;
  AlreadyLocked               : Boolean;
  tempHdr                     : MsgHdrType;
  Err                         : SysInt;
  S                           : String;

Begin
  If aHld or aDir or aImm or aLock Then
  Begin
    S := '';
    If aHld Then S := S + 'HLD ';
    If aDir Then S := S + 'DIR ';
    If aImm Then S := S + 'IMM ';
    If aLock Then S := S + 'LOK ';
    S := Trim (S);
    DoStringLn ('');
    DoKludgeLn (#1'FLAGS ' + S);
  End;

  tempHdr := MsgRec^. MsgHdr;
  shSeekFile (MsgRec^. MsgHdrFile, MsgRec^. MsgPos);
  shRead (MsgRec^. MsgHdrFile, MsgRec^. MsgHdr, 1, Err);
  WriteError := shIOResult;

  With MsgRec^. MsgHdr Do
  Begin
    DestNet  := tempHdr. DestNet;
    DestNode := tempHdr. DestNode;
    OrigNet  := tempHdr. OrigNet;
    OrigNode := tempHdr. OrigNode;
    DestZone := tempHdr. DestZone;
    OrigZone := tempHdr. OrigZone;
    MsgAttr  := tempHdr. MsgAttr;
    NetAttr  := tempHdr. NetAttr;
    MsgTo    := tempHdr. MsgTo;
    MsgFrom  := tempHdr. MsgFrom;
    Subj     := tempHdr. Subj;
    Time     := tempHdr. Time;
    Date     := tempHdr. Date;
  End;

  If WriteError = 0 Then WriteError := OpenAll (fmReadWrite, True);

  shSeekFile (MsgRec^. MsgHdrFile, MsgRec^. MsgPos);
  MsgRec^. MsgHdr. StartRec := shFileSize (MsgRec^. MsgTxtFile);
  shSeekFile (MsgRec^. MsgTxtFile, MsgRec^. MsgHdr. StartRec);

  If shFileSize (MsgRec^. MsgTxtFile) > $ff00 Then WriteError := 99 Else WriteError := 0;
  MsgRec^. MsgHdr. NumRecs := MsgRec^. CurrTxtRec;
  MsgChars^ [MsgRec^. CurrTxtRec] [0] := Chr (MsgRec^. CurrTxtPos);

  Case MsgRec^. MT Of

    mmtNormal   : Begin
                    MsgRec^. MsgHdr. MsgAttr := MsgRec^. MsgHdr. MsgAttr And
                    Not (maNetMail + maUnmovedNet + maUnmovedEcho);
                  End;

    mmtEchoMail : Begin
                    MsgRec^. MsgHdr. MsgAttr := MsgRec^. MsgHdr. MsgAttr And
                    Not (maNetMail + maUnmovedNet);
                    {If MsgRec^. Echo Then} MsgRec^. MsgHdr. MsgAttr := MsgRec^. MsgHdr. MsgAttr Or maUnmovedEcho;
                  End;

    mmtNetMail  : Begin
                    MsgRec^. MsgHdr. MsgAttr := MsgRec^. MsgHdr. MsgAttr And Not (maUnmovedEcho);
                    MsgRec^. MsgHdr. MsgAttr := MsgRec^. MsgHdr. MsgAttr Or maNetMail;
                    {If MsgRec^. Echo Then} MsgRec^. MsgHdr. MsgAttr := MsgRec^. MsgHdr. MsgAttr Or maUnmovedNet;
                  End;
  End;

  MsgRec^. MsgHdr. Area := MsgRec^. Area;
  AlreadyLocked := MsgRec^. Locked;

  If Not AlreadyLocked Then
  If Not LockMsgBase Then WriteError := 5;

  If WriteError = 0 Then               {Write MsgHdr}
  If Not shWrite (MsgRec^. MsgHdrFile, MsgRec^. MsgHdr, 1) Then WriteError := shIOResult;

  If WriteError = 0 Then               {Write MsgTxt}
  If Not shWrite (MsgRec^. MsgTxtFile, MsgChars^, MsgRec^. MsgHdr. NumRecs) Then WriteError := shIOResult;

  If WriteError = 0 Then ReWriteHdr;
  If WriteError = 0 Then
  If Not AlreadyLocked Then
  If Not UnlockMsgBase Then WriteError := 5;

  WriteError := OpenAll (fmReadOnly, True);
  ReWriteMsg := WriteError;
End;

Procedure HudsonMsgObj. SetDest (Var Addr: AddrType);   {Set Zone/Net/Node/Point for Dest}
Begin
  MsgRec^. MsgHdr. DestZone := Lo (Addr. Zone);
  MsgRec^. MsgHdr. DestNet := Addr. Net;
  MsgRec^. MsgHdr. DestNode := Addr. Node;
  MsgRec^. DestPoint := Addr. Point;
  If ((MsgRec^. DestPoint <> 0) And (MsgRec^. Mt = mmtNetMail)) Then
       DoStringLn (#1 + 'TOPT ' + Long2Str (MsgRec^. DestPoint));
End;

Procedure HudsonMsgObj. SetOrig (Var Addr: AddrType);   {Set Zone/Net/Node/Point for Orig}
Begin
  MsgRec^. MsgHdr. OrigZone := Lo (Addr. Zone);
  MsgRec^. MsgHdr. OrigNet := Addr. Net;
  MsgRec^. MsgHdr. OrigNode := Addr. Node;
  MsgRec^. OrigPoint := Addr. Point;
  If ((MsgRec^. OrigPoint <> 0) And (MsgRec^. Mt = mmtNetmail)) Then
     DoStringLn (#1 + 'FMPT ' + Long2Str (MsgRec^. OrigPoint));
End;

Const
  HdrArrSize = {$IFDEF NOT_TOR} 250 {$ELSE} 40 {$ENDIF};

Type
  tHdrArray = Array [1..HdrArrSize] Of MsgHdrType;

Var
  HdrArray   : ^tHdrArray;
  Actual     : SysInt;

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

Procedure HudsonMsgObj. GetTotalMsgs;
Var
  i, PrevNum  : Word;

Begin
  FileMode := fmReadOnly;
  If shReSet (MsgRec^. MsgHdrFile, SizeOf (MsgHdrType)) Then
  Begin
    New (HdrArray);
    PrevNum := 0;

    While Not shEoF (MsgRec^. MsgHdrFile) Do
    Begin
      If Not shRead (MsgRec^. MsgHdrFile, HdrArray^, HdrArrSize, Actual) Then Break;

      For i := 1 To Actual Do
      If  (HdrArray^ [i]. MsgNum <> 65535) And
          (HdrArray^ [i]. Area = MsgRec^. Area) And
         ((HdrArray^ [i]. MsgAttr And maDeleted) = 0) And
          (HdrArray^ [i]. MsgNum > PrevNum) Then
      Begin
        PrevNum := HdrArray^ [i]. MsgNum;
        C^. Insert (Pointer (HdrArray^ [i]. MsgNum));
      End;

      If Actual < HdrArrSize Then Break;
    End;

    Dispose (HdrArray);
  End;
End;

Procedure HudsonMsgObj. SetMsgPath (MP: String);
Begin
  MsgRec^. Area := Str2Long (Copy (MP, 1, 3));
  MsgRec^. MsgPath := AddBackSlash (Copy (MP, 4, 60));
  shAssign (MsgRec^. MsgIdxFile, MsgRec^. MsgPath + 'MSGIDX.BBS');
  shAssign (MsgRec^. MsgToIdxFile, MsgRec^. MsgPath + 'MSGTOIDX.BBS');
  shAssign (MsgRec^. MsgHdrFile, MsgRec^. MsgPath + 'MSGHDR.BBS');
  shAssign (MsgRec^. MsgTxtFile, MsgRec^. MsgPath + 'MSGTXT.BBS');
  shAssign (MsgRec^. MsgInfoFile, MsgRec^. MsgPath + 'MSGINFO.BBS');
End;

Function HudsonMsgObj. LockMsgBase: Boolean; {Lock msg base prior to adding message}
Var
  LockError     : Word;
  NumRead       : SysInt;

Begin
  LockError := 0;

  If Not MsgRec^. Locked Then
  Begin
    LockError := shLock (MsgRec^. MsgInfoFile, 406, 1);
    If LockError = 1 Then LockError := 0;   {No Locking if share not loaded}

    If LockError = 0 Then
    Begin
      shSeekFile (MsgRec^. MsgInfoFile, 0);
      LockError := shIOResult;
    End;

    If LockError = 0 Then
    If Not shRead (MsgRec^. MsgInfoFile, MsgRec^. MsgInfo, 1, NumRead) Then LockError := shIOResult;
  End;

  MsgRec^. Locked := (LockError = 0);
  LockMsgBase := LockError = 0;
End;

Function HudsonMsgObj. UnlockMsgBase: Boolean; {Unlock msg base after adding message}
Var
  LockError     : Word;

Begin
  If MsgRec^. Locked Then
  Begin
    shSeekFile (MsgRec^. MsgInfoFile, 0);
    LockError := shIOResult;
    shWrite (MsgRec^. MsgInfoFile, MsgRec^. MsgInfo, 1);
    If LockError = 0 Then shIOResult;
    LockError := UnLockFile (MsgRec^. MsgInfoFile, 406, 1);
    If LockError = 1 Then LockError := 0;  {No locking if share not loaded}
  End Else
    LockError := 0;

  MsgRec^. Locked := False;
  UnlockMsgBase := LockError = 0;
End;

Function HudsonMsgObj. GetNumActive: Word;
Begin
  GetNumActive := MsgRec^. MsgInfo. Active;
End;

Function HudsonMsgObj. CreateMsgBase (MaxMsg: Word; MaxDays: Word): Word;
Var
  CreateError           : Word;

Begin
  CreateError := 0;
  If Not MakePath (MsgRec^. MsgPath) Then CreateError := 1;
  shReWrite (MsgRec^. MsgIdxFile, SizeOf (MsgIdxType));
  If CreateError = 0 Then CreateError := shIOResult;
  shReWrite (MsgRec^. MsgToIdxFile, SizeOf (MsgToIdxType));
  If CreateError = 0 Then CreateError := shIOResult;
  shReWrite (MsgRec^. MsgHdrFile, SizeOf (MsgHdrType));
  If CreateError = 0 Then CreateError := shIOResult;
  shReWrite (MsgRec^. MsgTxtFile, SizeOf (MsgTxtType));
  If CreateError = 0 Then CreateError := shIOResult;
  shReWrite (MsgRec^. MsgInfoFile, SizeOf (MsgInfoType));
  If CreateError = 0 Then CreateError := shIOResult;
  MsgRec^. MsgInfo. LowMsg := 1;
  MsgRec^. MsgInfo. HighMsg := 0;
  MsgRec^. MsgInfo. Active := 0;
  FillChar (MsgRec^. MsgInfo. AreaActive [1], 200, #0);
  If Not shWrite (MsgRec^. MsgInfoFile, MsgRec^. MsgInfo, 1) Then CreateError := shIOResult;
  shClose (MsgRec^. MsgInfoFile);
  If CreateError = 0 Then CreateError := shIOResult;
  shClose (MsgRec^. MsgIdxFile);
  If CreateError = 0 Then CreateError := shIOResult;
  shClose (MsgRec^. MsgToIdxFile);
  If CreateError = 0 Then CreateError := shIOResult;
  shClose (MsgRec^. MsgTxtFile);
  If CreateError = 0 Then CreateError := shIOResult;
  shClose (MsgRec^. MsgHdrFile);
  If CreateError = 0 Then CreateError := shIOResult;
  CreateMsgBase := CreateError;
  shIOResult;
End;

Function  HudsonMsgObj. MsgBaseExists: Boolean;
Begin
  MsgBaseExists := (Check <> 1);
End;

Function HudsonMsgObj. Check: Word;
  {Check if msg base is Ok}
  { 0 = ok, 1 = not there (create), 2 = corrupted}

Var
  BaseSize      : LongInt;
  Status        : Word;

Begin
  Status := 0;
  If (Not FileExists (MsgRec^. MsgPath + 'MSGINFO.BBS')) Then Status := 1;

  If (Not FileExists (MsgRec^. MsgPath + 'MSGHDR.BBS')) Then
  Begin
    If Status = 0 Then Status := 2;
  End Else
  Begin
    If Status = 1 Then Status := 2;
  End;

  If (Not FileExists (MsgRec^. MsgPath + 'MSGTXT.BBS')) Then
  Begin
    If Status = 0 Then Status := 2;
  End Else
    If Status = 1 Then Status := 2;

  If (Not FileExists (MsgRec^. MsgPath + 'MSGIDX.BBS')) Then
  Begin
    If Status = 0 Then Status := 2;
  End Else
    If Status = 1 Then Status := 2;

  If (Not FileExists (MsgRec^. MsgPath + 'MSGTOIDX.BBS')) Then
  Begin
    If Status = 0 Then Status := 2;
  End Else
    If Status = 1 Then Status := 2;

  If Status = 0 Then
  If gFileSize (MsgRec^. MsgPath + 'MSGINFO.BBS') <> SizeOf (MsgInfoType) Then Status := 2;

  If Status = 0 Then
  Begin
    BaseSize := gFileSize (MsgRec^. MsgPath + 'MSGHDR.BBS') Div SizeOf (MsgHdrType);
    If BaseSize <> gFileSize (MsgRec^. MsgPath + 'MSGIDX.BBS') Div SizeOf (MsgIdxType) Then Status := 2;
    If BaseSize <> gFileSize (MsgRec^. MsgPath + 'MSGTOIDX.BBS') Div SizeOf (MsgToIdxType) Then Status := 2;
  End;

  Check := Status;
End;

Function HudsonMsgObj. MsgBaseSize: Word;
Begin
  If Length (MsgRec^. MsgPath) > 0
  Then
    MsgBaseSize := shFileSize (MsgRec^. MsgIdxFile)
  Else
    MsgBaseSize := 0;
End;

Function HudsonMsgObj. SeekEnd: Word;        {Seek to end of Msg Base Files}
  Var
    SeekError: Word;

  Begin
    SeekError := 0;
    shSeekFile (MsgRec^. MsgIdxFile, shFileSize (MsgRec^. MsgIdxFile));
    If SeekError = 0 Then
      SeekError := shIOResult;
    shSeekFile (MsgRec^. MsgToIdxFile, shFileSize (MsgRec^. MsgToIdxFile));
    If SeekError = 0 Then
      SeekError := shIOResult;
    shSeekFile (MsgRec^. MsgTxtFile, shFileSize (MsgRec^. MsgTxtFile));
    If SeekError = 0 Then
      SeekError := shIOResult;
    shSeekFile (MsgRec^. MsgHdrFile, shFileSize (MsgRec^. MsgHdrFile));
    If SeekError = 0 Then
      SeekError := shIOResult;
    SeekEnd := SeekError;
  End;

Function HudsonMsgObj. SeekMsgBasePos (Position: Word): Word; {Seek to pos of Msg Base File}
Var
  SeekError: Word;

Begin
  shSeekFile (MsgRec^. MsgIdxFile, Position);
  SeekError := shIOResult;
  shSeekFile (MsgRec^. MsgToIdxFile, Position);
  If SeekError = 0 Then SeekError := shIOResult;
  shSeekFile (MsgRec^. MsgHdrFile, Position);
  If SeekError = 0 Then SeekError := shIOResult;
  SeekMsgBasePos := SeekError;
End;

Function HudsonMsgObj. WriteMailIdx (FN: String; MsgPos: Word): Word; {Write Netmail or EchoMail.Bbs}
Var
  IdxFile       : shFile;
  WriteError    : Word;
  IdxName       : String;

Begin
  If Length (HudsonEcho) > 0
  Then IdxName := AddBackSlash (HudsonEcho) + FN
  Else IdxName := MsgRec^. MsgPath + FN;

  shAssign (IdxFile, IdxName);
  FileMode := fmReadWrite;

  If FileExists (IdxName) Then
  Begin
    shReset (IdxFile, SizeOf (MsgPos));
    shSeekFile (IdxFile, shFileSize (IdxFile));
    WriteError := shIOResult;
  End Else
  Begin
    shReset (IdxFile, SizeOf (MsgPos));
    WriteError := shIOResult;
  End;

  If WriteError = 0 Then
  Begin
    shWrite (IdxFile, MsgPos, 1);
    WriteError := shIOResult;
  End;

  If WriteError = 0 Then
  Begin
    shClose (IdxFile);
    WriteError := shIOResult;
  End;

  WriteMailIdx := WriteError;
End;

Function HudsonMsgObj. OpenAll (Mode: Word; CloseBefore: Boolean): Word;
Var
  fi1, fi2, fi3, fi4, fi5 : LongInt;

Begin
  FileMode := Mode;
  OpenAll := 0;

  If CloseBefore Then
  Begin
    fi1 := shFilePos (MsgRec^. MsgIdxFile);
    fi2 := shFilePos (MsgRec^. MsgToIdxFile);
    fi3 := shFilePos (MsgRec^. MsgHdrFile);
    fi4 := shFilePos (MsgRec^. MsgTxtFile);
    fi5 := shFilePos (MsgRec^. MsgInfoFile);
    CloseMsgBase;
  End;

  If Not shReset (MsgRec^. MsgIdxFile, SizeOf (MsgIdxType)) Then OpenAll := shIOResult;
  If Not shReset (MsgRec^. MsgToIdxFile, SizeOf (MsgToIdxType)) Then OpenAll := shIOResult;
  If Not shReset (MsgRec^. MsgTxtFile, SizeOf (MsgTxtType)) Then OpenAll := shIOResult;
  If Not shReset (MsgRec^. MsgHdrFile, SizeOf (MsgHdrType)) Then OpenAll := shIOResult;
  If Not shReset (MsgRec^. MsgInfoFile, SizeOf (MsgInfoType)) Then OpenAll := shIOResult;

  If CloseBefore Then
  Begin
    shSeekFile (MsgRec^. MsgIdxFile, fi1);
    shSeekFile (MsgRec^. MsgToIdxFile, fi2);
    shSeekFile (MsgRec^. MsgHdrFile, fi3);
    shSeekFile (MsgRec^. MsgTxtFile, fi4);
    shSeekFile (MsgRec^. MsgInfoFile, fi5);
  End;
End;

Function HudsonMsgObj. OpenMsgBase: Word; {Set path and initialize}
Var
  OpenError             : Word;
  NumRead               : SysInt;

Begin
  OpenError := 0;

  If Not MsgRec^. Opened Then
  If Check = 0 Then OpenError := OpenAll (fmReadOnly, False) Else
  Begin
    OpenMsgBase := 1;
    Exit;
  End;

  If OpenError = 0 Then
  If Not shRead (MsgRec^. MsgInfoFile, MsgRec^. MsgInfo, 1, NumRead) Then OpenError := 1;

  MsgRec^. Opened := (OpenError = 0);
  OpenMsgBase := OpenError;
End;

Function HudsonMsgObj. CloseMsgBase: Word;         {shClose Msg Base Files}
Var
  CloseError    : Word;

Begin
  CloseError := 0;
  If MsgRec^. Opened Then
  Begin
    shClose (MsgRec^. MsgIdxFile); If CloseError = 0 Then CloseError := shIOResult;
    shClose (MsgRec^. MsgToIdxFile); If CloseError = 0 Then CloseError := shIOResult;
    shClose (MsgRec^. MsgTxtFile); If CloseError = 0 Then CloseError := shIOResult;
    shClose (MsgRec^. MsgHdrFile); If CloseError = 0 Then CloseError := shIOResult;
    shClose (MsgRec^. MsgInfoFile); If CloseError = 0 Then CloseError := shIOResult;
  End;
  CloseMsgBase := CloseError;
End;

Procedure HudsonMsgObj. SeekRead (NumToRead: Word);
Begin
  If NumToRead > SeekSize Then NumToRead := SeekSize;
  shSeekFile (MsgRec^. MsgIdxFile, MsgRec^. SeekStart);
  shIOResult;
  If Not shRead (MsgRec^. MsgIdxFile, SeekArray^, NumToRead, MsgRec^. SeekNumRead)
  Then MsgRec^. Error := 1000;
End;

Procedure HudsonMsgObj. SeekNext;
Var
  SDone : Boolean;

Begin
  MsgRec^. SeekOver := C^. Count = 0;
  If MsgRec^. SeekOver And FoundNext Then Exit;

  SDone := False;
  FoundNext := True;

  While Not SDone Do
  Begin
    Inc (MsgRec^. SeekPos);

    If MsgRec^. SeekPos > MsgRec^. SeekNumRead Then
    Begin
      Inc (MsgRec^. SeekStart, MsgRec^. SeekNumRead);
      SeekRead (SeekSize);
      If MsgRec^. SeekNumRead <> 0 Then MsgRec^. SeekPos := 1;
    End;

    If MsgRec^. SeekNumRead = 0 Then
    Begin
      SeekFirst (GetHighMsgNum);
      MsgRec^. SeekOver := True;
      SDone := True;
    End Else
    Begin
      If ((SeekArray^ [MsgRec^. SeekPos]. MsgNum > MsgRec^. CurrMsgNum) And
          (SeekArray^ [MsgRec^. SeekPos]. MsgNum <> $ffff) And
          (SeekArray^ [MsgRec^. SeekPos]. Area = MsgRec^. Area) And
          (MsgRec^. SeekPos > 0) And (MsgRec^. SeekPos <= MsgRec^. SeekNumRead)) Then
      Begin
        SDone := True;
        MsgRec^. CurrMsgNum := SeekArray^ [MsgRec^. SeekPos].MsgNum;
      End;
    End;
  End;

  MsgRec^. MsgPos := MsgRec^. SeekStart + MsgRec^. SeekPos - 1;
  If Not MsgRec^. SeekOver Then CurMsg := Num2Relative (MsgRec^. CurrMsgNum);
End;

Procedure HudsonMsgObj. SeekPrior;
Var
  SDone         : Boolean;
  SeekDec       : Word;

Begin
  MsgRec^. SeekOver := C^. Count = 0;
  If MsgRec^. SeekOver And Not FoundNext Then Exit;

  SDone := False;
  MsgRec^. SeekOver := False;
  FoundNext := False;

  While Not SDone Do
  Begin
    Dec (MsgRec^. SeekPos);

    If MsgRec^. SeekPos < 1 Then
    Begin
      If MsgRec^. SeekStart = 0 Then
      Begin
        SeekFirst (GetLowMsgNum);
        MsgRec^. SeekOver := True;
        SDone := True;
        FoundNext := False;
      End;

      If (MsgRec^. SeekStart < SeekSize)
      Then SeekDec := MsgRec^. SeekStart
      Else SeekDec := SeekSize;

      Dec (MsgRec^. SeekStart, SeekDec);
      If MsgRec^. SeekStart < 0 Then MsgRec^. SeekStart := 0;
      SeekRead (SeekDec);
      MsgRec^. SeekPos := MsgRec^. SeekNumRead;
    End;

    If Not MsgRec^. SeekOver Then
    Begin
      If ((SeekArray^ [MsgRec^. SeekPos]. MsgNum < MsgRec^. CurrMsgNum) And
          (SeekArray^ [MsgRec^. SeekPos]. MsgNum <> $ffff) And
          (SeekArray^ [MsgRec^. SeekPos]. Area = MsgRec^. Area) And
          (MsgRec^. SeekPos > 0) And (MsgRec^. SeekPos <= MsgRec^. SeekNumRead))
      Then Begin
        SDone := True;
        MsgRec^. CurrMsgNum := SeekArray^ [MsgRec^. SeekPos]. MsgNum;
      End;
    End;
  End;

  MsgRec^. MsgPos := MsgRec^. SeekStart + MsgRec^. SeekPos - 1;
  If Not MsgRec^. SeekOver Then CurMsg := Num2Relative (MsgRec^. CurrMsgNum);
End;

Function HudsonMsgObj. SeekFound: Boolean;   {Seek has been completed}
Begin
  SeekFound := Not MsgRec^. SeekOver;
End;

Procedure HudsonMsgObj. SeekFirst (MsgNum: LongInt);
Begin
  MsgRec^. SeekStart := 0;
  MsgRec^. SeekNumRead := 0;
  MsgRec^. SeekPos := 0;
  MsgRec^. SeekOver := False;
  SeekRead (SeekSize);
  MsgRec^. CurrMsgNum := MsgNum - 1;
  SeekNext;
End;

Procedure HudsonMsgObj. SetMailType (MT: MsgMailType);
Begin
  MsgRec^. MT := MT;
End;

Function HudsonMsgObj. GetSubArea: Word;
Begin
  GetSubArea := MsgRec^. MsgHdr. Area;
End;

Procedure HudsonMsgObj. ReWriteHdr;
Var
  RcvdName : String [35];
  MsgError : Word;
  MsgIdx   : MsgIdxType;
  FN       : String;

Begin
  MsgError := OpenAll (fmReadWrite, True);

  If MsgError = 0 Then MsgError := SeekMsgBasePos (MsgRec^. MsgPos);
  If IsRcvd Then RcvdName := '* Received *' Else RcvdName := MsgRec^. MsgHdr. MsgTo;

  If IsDeleted Then
  Begin
    RcvdName := '* Deleted *';
    MsgIdx. MsgNum := $ffff;
  End Else
    MsgIdx. MsgNum := MsgRec^. MsgHdr. MsgNum;

  If MsgError = 0 Then
  If Not shWrite (MsgRec^. MsgHdrFile, MsgRec^. MsgHdr, 1) Then MsgError := shIOResult;

  If MsgError = 0 Then
  If Not shWrite (MsgRec^. MsgToIdxFile, RcvdName, 1) Then MsgError := shIOResult;

  MsgIdx. Area := MsgRec^. MsgHdr. Area;

  If MsgError = 0 Then
  If Not shWrite (MsgRec^. MsgIdxFile, MsgIdx, 1) Then shIOResult;

  Case MsgRec^. MT Of
    mmtEchoMail : FN := 'ECHOMAIL.BBS';
    mmtNetMail  : FN := 'NETMAIL.BBS';
  Else
    FN := '';
  End; {Case MsgType}

  If Length (FN) > 0 Then WriteMailIdx (FN, MsgRec^. MsgPos);
  OpenAll (fmReadOnly, True);
End;

Procedure HudsonMsgObj. DeleteMsg (CarePos: Boolean);
Var
  NumRead            : SysInt;
  RcvdName           : String [35];
  MsgIdx             : MsgIdxType;
  MsgError           : Word;
  IsBegin, IsEnd     : Boolean;

Begin
  IsBegin := (CurMsg = 1);
  IsEnd   := (CurMsg = C^. Count);
  MsgIdx. Area := MsgRec^. MsgHdr. Area;

  If LockMsgBase Then MsgError := 0 Else MsgError := 5;
  If MsgError = 0 Then MsgError := SeekMsgBasePos (MsgRec^. MsgPos);

  If MsgError = 0 Then
  If Not shRead (MsgRec^. MsgHdrFile, MsgRec^. MsgHdr, 1, NumRead) Then MsgError := shIOResult;

  If ((MsgRec^. MsgHdr. MsgAttr And maDeleted) = 0) Then
  Begin
    Dec (MsgRec^. MsgInfo. Active);
    Dec (MsgRec^. MsgInfo. AreaActive [MsgRec^. MsgHdr. Area] );
  End;

  MsgRec^. MsgHdr. MsgAttr := MsgRec^. MsgHdr. MsgAttr Or maDeleted;
  RcvdName := '* Deleted *';
  MsgIdx. MsgNum := $ffff;
  If MsgError = 0 Then MsgError := SeekMsgBasePos (MsgRec^. MsgPos);

  If MsgError = 0 Then
  If Not shWrite (MsgRec^. MsgHdrFile, MsgRec^. MsgHdr, 1) Then MsgError := shIOResult;

  If MsgError = 0 Then
  If Not shWrite (MsgRec^. MsgToIdxFile, RcvdName, 1) Then MsgError := shIOResult;

  If MsgError = 0 Then
  If Not shWrite (MsgRec^. MsgIdxFile, MsgIdx, 1) Then MsgError := shIOResult;

  If MsgError = 0 Then UnLockMsgBase;

  ReWriteHdr;
  (*
  FreeMem (C^. At (CurMsg-1), 4);
  *)
  C^. AtDelete (CurMsg-1);
  SeekArray^ [MsgRec^. SeekPos]. MsgNum := 65535;

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

Function HudsonMsgObj. GetMsgLoc: LongInt;
Begin
  GetMsgLoc := MsgRec^. MsgPos;
End;

Procedure HudsonMsgObj. SetMsgLoc (ML: LongInt);
Begin
  MsgRec^. MsgPos := ML;
End;

Procedure HudsonMsgObj. GetAllLastRead (UNum: LongInt; Var LR: LastReadType);
Var
  LastName      : String;

Begin
  If Length (HudsonLast) > 0 Then
    LastName := AddBackSlash (HudsonLast) + 'LastRead.Bbs'
  Else
    LastName := MsgRec^. MsgPath + 'LastRead.Bbs';

  FillChar (LR, SizeOf (LR), 0);

  If ((UNum + 1) * SizeOf (LastReadType)) <= gFileSize (LastName) Then
  If LoadFilePos (LastName, LR, SizeOf (LR), UNum * SizeOf (LastReadType)) = 0 Then;
End;

Function HudsonMsgObj. GetLastRead (UNum: LongInt): LongInt;
Var
  LRec          : LastReadType;
  LastName      : String;

Begin
  If Length (HudsonLast) > 0 Then
    LastName := AddBackSlash (HudsonLast) + 'LastRead.Bbs'
  Else
    LastName := MsgRec^. MsgPath + 'LastRead.Bbs';

  If ((UNum + 1) * SizeOf (LastReadType)) > gFileSize (LastName)
  Then GetLastRead := 0 Else
  If LoadFilePos (LastName, LRec, SizeOf (LRec), UNum * SizeOf (LastReadType)) = 0
  Then
    GetLastRead := LRec [MsgRec^. Area]
  Else
    GetLastRead := 0;
End;

Procedure HudsonMsgObj. SetLastRead (UNum: LongInt; LR: LongInt);
Var
  LRec          : LastReadType;
  LastName      : String; {path\filename of lastread.bbs}

Begin
  If LR = 0 Then Exit;

  If Length (HudsonLast) > 0 Then
    LastName := AddBackSlash (HudsonLast) + 'LastRead.Bbs'
  Else
    LastName := MsgRec^. MsgPath + 'LastRead.Bbs';

  If ((UNum + 1) * SizeOf (LastReadType)) >  gFileSize (LastName) Then
    ExtendFile (LastName, (UNum + 1) * SizeOf (LastReadType));

  If LoadFilePos (LastName, LRec, SizeOf (LRec), UNum * SizeOf (LastReadType)) = 0 Then
  Begin
    LRec [MsgRec^. Area] := LR;
    SaveFilePos (LastName, LRec, SizeOf (LRec), UNum * SizeOf (LastReadType));
  End;
End;

Procedure HudsonMsgObj. GetHighest (Var LR: LastReadType);
Var
  i       : Word;
  IdxFile : shFile;
  MIdx    : ^SeekArrayType;
  NumRead : SysInt;

Begin
  New (MIdx);
  For i := 1 To 200 Do LR [i] := 0;
  shAssign (IdxFile, MsgRec^. MsgPath + 'MsgIdx.Bbs');
  FileMode := fmReadOnly;
  shReset (IdxFile, SizeOf (MsgIdxType));

  While Not shEoF (IdxFile) Do
  Begin
    shRead (IdxFile, MIdx^, SeekSize, NumRead);
    i := 1;
    While i <= NumRead Do
    Begin
      If MIdx^ [i].MsgNum <> $ffff Then
      If MIdx^ [i].MsgNum > LR [MIdx^ [i].Area]
      Then LR [MIdx^ [i].Area] := MIdx^ [i].MsgNum;
      Inc (i);
    End;
  End;

  shClose (IdxFile);
  shIOResult;
  Dispose (MIdx);
End;

Function HudsonMsgObj. GetTxtPos: LongInt;
Var
  Tmp   : LongInt;

Begin
  Tmp := MsgRec^. CurrTxtRec;
  GetTxtPos := MsgRec^. CurrTxtPos + Tmp ShL 16;
End;

Procedure HudsonMsgObj. SetTxtPos (TP: LongInt);
Begin
  MsgRec^. CurrTxtRec := TP ShR 16;
  MsgRec^. CurrTxtPos := TP And $ffff;
End;

Function HudsonMsgObj. GetString (MaxLen: Word): String;
Var
  Rec, PPos, WRec, WPos         : Word;
  CurrLen, WLen                 : Byte;
  StrDone, TxtOver, StartSoft   : Boolean;
  Tmp                           : String;

Begin
  Tmp := Replicate (' ', 255);
  StrDone := False;
  CurrLen := 0;
  Rec := MsgRec^. CurrTxtRec;
  PPos := MsgRec^. CurrTxtPos;
  TxtOver := Not NextChar (Rec, PPos);
  If TxtOver Then MsgRec^. EOM := True;
  WLen := 0;
  WRec := Rec;
  WPos := PPos;
  StartSoft := LastSoft;
  LastSoft := True;
  While ((Not StrDone) And (CurrLen < MaxLen) And (Not TxtOver)) Do
  Begin
    Case MsgChars^ [Rec] [PPos] Of
      #$0D:
            Begin
              StrDone := True;
              LastSoft := False;
            End;
      #$8D:;
      #$0a:;
      #$20: Begin
              If ((CurrLen <> 0) Or (Not StartSoft)) Then
              Begin
                Inc (CurrLen);
                Tmp [CurrLen] := MsgChars^ [Rec] [PPos];
                WLen := CurrLen;
                WRec := Rec;
                WPos := PPos;
              End
              Else
                StartSoft := False;
            End;
    Else
      Inc (CurrLen);
      Tmp [CurrLen] := MsgChars^ [Rec] [PPos];
    End;

    Inc (PPos);
    TxtOver := Not NextChar (Rec, PPos);
  End;

  If StrDone Then
  Begin
    SetLength (Tmp, CurrLen);
    MsgRec^. CurrTxtRec := Rec;
    MsgRec^. CurrTxtPos := PPos;
  End Else
  If TxtOver Then
  Begin
    MsgRec^. CurrTxtRec := Rec;
    MsgRec^. CurrTxtPos := PPos;
    If CurrLen = 0 Then MsgRec^. EOM := True;
  End Else
  Begin
    If WLen = 0 Then
    Begin
      SetLength (Tmp, CurrLen);
      MsgRec^. CurrTxtRec := Rec;
      MsgRec^. CurrTxtPos := PPos;
    End Else
    Begin
      SetLength (Tmp, WLen);
      Inc (WPos);
      NextChar (WRec, WPos);
      MsgRec^. CurrTxtPos := WPos;
      MsgRec^. CurrTxtRec := WRec;
    End;
  End;

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

Function HudsonMsgObj. IsUns: Boolean;
Begin
  If MsgRec^. MT = mmtNetMail
  Then IsUns := ((MsgRec^. MsgHdr. MsgAttr And maUnmovedNet) <> 0)
  Else IsUns := ((MsgRec^. MsgHdr. MsgAttr And maUnmovedEcho) <> 0);
End;

End.