{MKMsgSqu - Copyright 1993 by Mark May - MK Software
 changes (c) 1999-2000 by Andre Grueneberg (2:2411/525;andre@grueneberg.de)
 changes (c) Copyright 1998,2000 Bernhard R. Link (2:2476/841.64;brl@gmx.de)
 changes (c) 2001 by Oliver Kopp (2:2471/1464;olly98@users.sourceforge.net)
****************************************************************************
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
****************************************************************************}
Unit MKMsgSqu;

{ $DEFINE UseTextDate} {define if AsciiZ-Date should be used instead of binary date}

{$I platform.inc}
{$I mkglobal.inc}

interface
Uses {$IFDEF PPC_VIRTUAL}
     use32,
     {$ENDIF}
     aTypes,aString, {independent aXX-Types}
     uDate,          {independent Date-Rotines}
     FTNAddr,        {Addresses e.a.}
     MKSqType,      {Types}
     MKSqIdx,       {Idices}
     MKSqIB,        {Buffer for SQI}
     MKMsgAbs;       {abstract MsgType}

{$IFDEF LongStrings}
Type TMKFN=Ansistring;
     TSNameStr=AnsiString;
{$ELSE}
Type
     TMKFN=String[80];
     TSNameStr=String[35];
{$ENDIF}

Type FreeListType = Record
  FreePos: TFileOfs;
  FreeSize: TFileOfs; {length ~ ofs  ;-) }
  End;

Const MaxFree = 500;

Type FreeArrayType = Array[1..MaxFree] of FreeListType;


Const
  sqStdBaseSize=SizeOf(sqBaseType);
  sqStdFrameHdrSize=SizeOf(sqFrameHdrType);
  sqMsgHdrSize = SizeOf(SqMsgHdrType);

Const sqStdTxtBufferSize=16*1024;{Anfangsgr"osse}
      sqAddTxtBufferSize=4*1024;{Zusatzgr"osse}

Type PsqAddMsgText=^TsqAddMsgText;
     TsqAddMsgText=Record
                   next:PsqAddMsgText;
                   data:Array[1..sqAddTxtBufferSize] of Char;
                   end;
     PsqMsgText=^TsqMsgText;
     TsqMsgText=Record
                add:PsqAddMsgText;
                data:Array[1..sqStdTxtBufferSize]of Char;
                end;

Type SqInfoType = Record
  FN: TMKFN;
  MsgText:PsqMsgText;{Pointer, There is not always a msg to edit}
  SqdFile: File;
  SqBase: SqBaseType;
  SqdOpened: Boolean;
  Locked: Boolean;
  FreeLoaded: Boolean;
  HighestFree: TIdxNum;
  Frame: SqFrameHdrType;
  MsgHdr: SqMsgHdrType;
  TxtCtr: longint;
  MsgDone: Boolean;
{(B}InCtrl:Byte;{0:eigentliche Nachricht;1:CtrlBlock;2:#1}{B)}
  CurrIdx: TIDXNum;
  StrDateTime:TDateTimeStr;
  CurrentFramePos: TFileOfs;
  CurrentUID: TUMid;
  SName: TSNameStr;
  SHandle: TSNameStr;
  HName: uint4;
  HHandle: uint4;
  End;

Type TMKSquish = Object(AbsMsgObj)
  SqInfo: ^SqInfoType;
  sqi:PSQI;
  FreeArray: ^FreeArrayType;

  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;

  Procedure SetMsgBasePath(const FN: OpenString); Virtual; {Set filepath and name - no extension}
  Procedure SetMsgBaseType(dummy: TMailType); virtual; {does nothing}

  Function  MsgBaseExists: Boolean; Virtual;

  Function  LockMsgBase: Boolean; Virtual; {Lock msg base}
  Function  UnLockMsgBase: Boolean; Virtual; {Unlock msg base}

  Function  NumberOfMsgs: LongInt; Virtual; {Number of messages}

  Function  GetHighMsgNum: LongInt;virtual; {Get highest msg number}

  Function  KillMsg(MsgNum: LongInt):Integer;virtual; {Kill msg msgnum}
  Function  GetLastRead(UNum: LongInt): LongInt; Virtual; {Get last read for user num}
  Procedure SetLastRead(UNum: LongInt; LR: LongInt); Virtual; {Set last read}

  Function SeekFirst(MsgNum: LongInt;Reload:Boolean):Boolean; Virtual; {Seeks to 1st msg >= MsgNum, true if msg=MsgNum or ( msgNum=0 and any msg found)}
  Function SeekFirstAfter(MsgNum: LongInt;Reload:Boolean):Boolean; Virtual; {Seeks to 1st msg > MsgNum, true if any msg found}
  Function SeekPrior:Boolean; Virtual;
  Function SeekNext:Boolean; Virtual;

  Function  GetMsgNum: LongInt; Virtual;

  (* never use this functions - use GetString *)
  Function  GetTxtPos: LongInt; Virtual; {Get indicator of msg text position, ALWAYS RETURNS -1!!}
  Procedure SetTxtPos(TP: LongInt); Virtual; {Set text position, DOES NOTHING!!}

  Function  MsgStartUp:Boolean; Virtual; {Set up message}
  Function  MsgTxtStartUp:Boolean; Virtual; {Set up for msg text}

  Function  GetChar: Char; Virtual;
  Function  GetString(MaxLen: Word): TString; Virtual;
  Function  EOM: Boolean; Virtual;

  Function  GetSubj: TString; Virtual; {Get message subject}
  Function  GetFromName: TString; Virtual; {Get message from}
  Function  GetFromAddr: TFTNAddr; Virtual;
  Function  GetToName: TString; Virtual; {Get message to}
  Function  GetToAddr: TFTNAddr; Virtual;

  Function  GetDateTime:TDateTimeStr; Virtual; {'dd mmm yyyy  hh:mm:ss'}
  Function  GetRDateTime:TDateTimeStr; Virtual;

  Function  GetRefer: LongInt; Virtual; {Get reply to of current msg}

  Function  IsFAttach: Boolean; Virtual; {Is current msg file attach}
  Function  IsFileReq: Boolean; Virtual; {Is current msg a file request}

  Function  IsCrash: Boolean; Virtual; {Is current msg crash}
  Function  IsDeleted: Boolean; Virtual; {Is current msg deleted}
  Function  IsEchoed: Boolean; Virtual; {Is current msg unmoved echomail msg}
  Function  IsLocal: Boolean; Virtual; {Is current msg local}
  Function  IsFwd: Boolean; Virtual; {Is current msg in transit}
  Function  IsHold: Boolean; Virtual; {Is current msg ist on hold}
  Function  IsKillSent: Boolean; Virtual; {Is current msg kill sent}
  Function  IsPriv: Boolean; Virtual; {Is current msg priviledged/private}
  Function  IsRcvd: Boolean; Virtual; {Is current msg received}
  Function  IsReqAud: Boolean; Virtual; {Is current msg request audit}
  Function  IsReqRct: Boolean; Virtual; {Is current msg request receipt}
  Function  IsRetRct: Boolean; Virtual; {Is current msg a return receipt}
  Function  IsSent: Boolean; Virtual; {Is current msg sent}

  Function  StartNewMsg:Boolean; Virtual; {Initialize msg header}
  Function  WriteMsg: Word; Virtual; {Write msg to msg base}
  Function  ReWriteHdr: Word; Virtual; {Rewrite msg header after changes}

  Procedure DoChar(Ch: Char); Virtual; {Add character to message text}
  Procedure DoKludgeLn(const Str:OpenString);virtual;
  Procedure DoString(const Str: OpenString); Virtual; {Add String to message text}
  Procedure DoStringLn(const Str: OpenString); Virtual; {Add string and newline to msg text}

  Procedure SetSubj(const Str: OpenString); Virtual; {Set message subject}
  Procedure SetFromName(const Str: OpenString); Virtual; {Set message from}
  Procedure SetFromAddr(const Addr: TFTNAddr); Virtual;
  Procedure SetToName(const Str: OpenString); Virtual; {Set message to}
  Procedure SetToAddr(const Addr: TFTNAddr); Virtual;

  Procedure SetDateTime(const s:TDateTimeStr);virtual;

  Procedure SetRefer(Num: LongInt); Virtual; {Set reply to of current msg}

  Procedure SetFAttach(St: Boolean); Virtual; {Set file attach status}
  Procedure SetFileReq(St: Boolean); Virtual; {Set file request status}

  Procedure SetCrash(St: Boolean); Virtual; {Set crash netmail status}
  Procedure SetEcho(ES: Boolean); Virtual; {Set echo status}
  Procedure SetLocal(St: Boolean); Virtual; {Set local status}
  Procedure SetFwd(St: Boolean); Virtual; {Set in transit status}
  Procedure SetHold(St: Boolean); Virtual; {Set file hold status}
  Procedure SetKillSent(St: Boolean); Virtual; {Set kill/sent netmail status}
  Procedure SetPriv(St: Boolean); Virtual; {Set priveledge vs public status}
  Procedure SetRcvd(St: Boolean); Virtual; {Set received status}
  Procedure SetReqAud(St: Boolean); Virtual; {Set request audit status}
  Procedure SetReqRct(St: Boolean); Virtual; {Set request receipt status}
  Procedure SetRetRct(St: Boolean); Virtual; {Set return receipt status}
  Procedure SetSent(St: Boolean); Virtual; {Set sent netmail status}


  (* new *)

  Procedure SetAttr(St: Boolean; Mask: LongInt); Virtual; {Set attribute}

private
  AheadChar   : Char;    {next character}
  AheadLooked : Boolean; {have we already looked for the next character}

  EndOfControlRegionReached : Boolean; {used to create a cr at the end of the last control-line}

  Function  GetNextChar: Char; {looks ahead next char, used for finding out if message is finished}

  Function  GetIdxFramePos: LongInt; Virtual;
  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 ReadBase:Integer; Virtual; {Read base data record}
  Function WriteBase:Integer; Virtual; {Write base data record}
  Function ReadFrame(FPos: LongInt):Integer; Virtual; {Read frame at FPos}
  Function ReadVarFrame(Var Frame: SqFrameHdrType; FPos: LongInt):Integer; Virtual; {Read frame at FPos into Frame}
  Function WriteFrame(FPos: LongInt):Integer; Virtual; {Write frame at FPos}
  Function WriteVarFrame(Var Frame: SqFrameHdrType; FPos: LongInt):Integer; Virtual;
  Function UnlinkFrame(Var Frame: SqFrameHdrType):Integer; Virtual; {Unlink frame from linked list}
  Function KillExcess:Integer; {Kill msg in excess of limit}
  Function FindFrame(Var FL: LongInt; Var FramePos: LongInt):Integer; Virtual;
  Function ReadMsgHdr(FPos: LongInt):Integer; Virtual; {Read msg hdr for frame at FPos}
  Function WriteMsgHdr(FPos: LongInt):Integer; Virtual; {Read msg hdr for frame at FPos}
  Function WriteText(FPos: LongInt):Integer; Virtual; {Write text buffer for frame at Fpos}
  Function  SqHashName(const Name: OpenString;Rcvd:Boolean): uint4; Virtual; {Convert name to hash value}
  Function InitText:Integer; Virtual;
  Procedure discardText;virtual;
  Function ReadText(FPos: LongInt):Integer; Virtual;
  Procedure LoadFree; Virtual; {Load freelist into memory}
  Function  GetHighWater: LongInt;virtual; {Get high water umsgid}
  End;


Type sqMsgObj = TMKSquish;
     SqMsgPtr = ^TMKSquish;
     PMKSquish=^TMKSquish;

implementation
Uses {Da muesste man auch noch ein wenig nacharbeiten:}
{$IFDEF PPC_Virtual}
Dos,
{$ELSE}
{$IFNDEF OS_LINUX}
{$IFDEF OS_WINDOWS}
{$IFNDEF PPC_DELPHI}
WinDos,
{$ENDIF}
SysUtils,
{$ELSE}
 Dos,
{$ENDIF}
{$ENDIF}
{$ENDIF}
UFiles,ustring
{$IFDEF UseErrDlg}
,ErrDlg{$ENDIF};

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 TMKSquish.Init;
  Begin
  New(SqInfo);
  If SqInfo<>nil then
    SqInfo^.MsgText:=nil;
  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;
  SqInfo^.SqdOpened := False;
  SqInfo^.FN := '';
  SqInfo^.Locked := False;
  SqInfo^.FreeLoaded := False;
  If not SQI_INIT(sqi) then
    begin
    If SqInfo <> Nil Then
      Dispose(SqInfo);
    If FreeArray <> Nil Then
      Dispose(FreeArray);
    Fail;
    exit;
    end
  End;


Destructor TMKSquish.Done;
  Begin
  discardText;
  If SqInfo^.SqdOpened Then
    SqdClose;
  SQI_Done(sqi);
  Dispose(FreeArray);
  Dispose(SqInfo);
  End;

Procedure TMKSquish.SetMsgBasePath(const FN: OpenString);
  Begin
  SqInfo^.FN := StripExt(StripExt(FExpand(FN),'SQI'),'SQD');
  End;


Function TMKSquish.OpenMsgBase: Word;
var t:Integer;
  Begin
  If SqiOpen = 0 Then
    Begin
    t:=SqdOpen;
    OpenMsgBase:=t;
    If t=0 then {neu}
      OpenMsgBase:=SQI_ReAssign(sqi,sqInfo^.sqBase.NumMsg)
     else
      sqiClose;
    End
  Else
    OpenMsgBase := 100;
  End;


Function TMKSquish.SqdOpen: Word;
  Var
    NumRead: Word;

  Begin
  If Not SqInfo^.SqdOpened Then
    Begin
    Assign(SqInfo^.SqdFile, SqInfo^.FN + '.sqd');
      FileMode := fmReadWrite + fmDenyNone;
    If Not shReset(SqInfo^.SqdFile, 1) Then
      SqdOpen := UFilesError
    Else
      Begin
      SqInfo^.SqdOpened := True;
      If Not shRead(SqInfo^.SqdFile, SqInfo^.SqBase, 2, NumRead) Then
        SqdOpen := UFilesError
       else
        begin
        If SqInfo^.SqBase.Len = 0 Then
          SqInfo^.SqBase.Len := sizeOf(sqBaseType);
        If SqInfo^.SqBase.Len > (SizeOf(SqBaseType) + 100) Then
          SqdOpen := 1001
         else
          Begin
          {SqBSize := SqInfo^.SqBase.Len; Ai, was wollen wir denn da rumschreiben?}
          sqdOpen:=ReadBase;
          End;
        End;
      End;
    End
  Else
    SqdOpen := 0;
  End;

Function TMKSquish.SqiOpen: Word;
  Begin
  sqiOpen:=SQI_Open(sqi,sqInfo^.FN)
  End;


Function TMKSquish.CloseMsgBase: Word;
  Begin
  SqdClose;
  SqiClose;
  CloseMsgBase := 0;
  End;


Function TMKSquish.CreateMsgBase(MaxMsg: Word; MaxDays: Word): Word;
  Begin
  If Not SqInfo^.SqdOpened Then
   If not CreateDir(SqInfo^.Fn) then
     CreateMsgBase:=255
    else
    Begin
    FillChar(SqInfo^.SqBase, SizeOf(SqInfo^.SqBase), 0);
    SqInfo^.SqBase.Len := sqStdBaseSize;
    SqInfo^.SqBase.SqFrameHdrSize := sqStdFrameHdrSize;
    SqInfo^.SqBase.UID := 1;
    SqInfo^.SqBase.NumMsg := 0;
    Str2Asciiz(SqInfo^.FN,  SqInfo^.SqBase.Base);
    SqInfo^.SqBase.MaxMsg := MaxMsg;
    SqInfo^.SqBase.KeepDays := MaxDays;
    SqInfo^.SqBase.EndFrame := SqInfo^.SqBase.Len;
    CreateMsgBase := SaveFile(SqInfo^.FN + '.sqd', SqInfo^.SqBase, sizeof(sqInfo^.sqBase));
    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 TMKSquish.MsgBaseExists: Boolean;
  Begin
  MsgBaseExists :=  FileExist(SqInfo^.FN + '.sqd');
  End;


Procedure TMKSquish.SqdClose;
  Begin
  If SqInfo^.SqdOpened Then
    Close(SqInfo^.SqdFile);
  If IOResult <> 0 Then;
  SqInfo^.SqdOpened := False;
  End;


Function TMKSquish.LockMsgBase: Boolean; {Lock msg base}
  Begin
  If Not SqInfo^.Locked Then
    Begin
    SqInfo^.FreeLoaded := False;
    If shLock(SqInfo^.SqdFile, 0, 1) = 0 then
      begin
      If(ReadBase=0)and(SQI_ReAssign(sqi,sqInfo^.sqBase.NumMsg)=0) then{SQI und SQD lesbar?}
        begin
        SqInfo^.Locked := true
        end
       else
        begin
        If UnLockFile(SqInfo^.SqdFile, 0, 1)<2 then;
        sqInfo^.Locked:=false;
        end
      end
     else
      sqInfo^.Locked:=false;
    LockMsgBase := SqInfo^.Locked;
    End
   else
    LockMsgBase:=false;
  End;


Function TMKSquish.UnLockMsgBase: Boolean; {Unlock msg base}
  Begin
  If SqInfo^.Locked Then
    Begin
    If WriteBase<>0 then;
    If SQI_Flush(sqi)<>0 then;
    SqInfo^.Locked := not ( UnLockFile(SqInfo^.SqdFile, 0, 1) < 2 );
    UnLockMsgBase := Not SqInfo^.Locked;
    End
   else
    UnLockMsgBase:=false
  End;


Procedure TMKSquish.SqiClose;
  Begin
  SQI_Close(sqi);
  End;


Function TMKSquish.ReadBase:Integer;
  Var
    NumRead: Word;
  Begin
  {Nur lesen, wofr wir Platz haben (sqBaseType). Zustzliche Erweiterungen
   sollen uns doch nicht den Speicher berschreiben :-) }
  If Not shReadEx(SqInfo^.SqdFile,0, SqInfo^.SqBase, sizeOf(sqBaseType), NumRead) Then
    ReadBase:= UFilesError
   else
  {Vorsichtsmanahme:}
    If NumRead<>sizeOf(sqBaseType) then
      begin
      ReadBase:= 801;
      exit
      end
  {--}
     else
      ReadBase:=0;
  If SqInfo^.SqBase.SqFrameHdrSize = 0 Then
    SQInfo^.SqBase.SqFrameHdrSize := SqStdFrameHdrSize
   else
    if sqInfo^.sqBase.sqFrameHdrSize<sqStdFrameHdrSize then
      ReadBase:=761;
  End;


Function TMKSquish.WriteBase:Integer;
  Begin
  {Nur schreiben, was wir kennen (sqBaseType,nicht sqBase.len),
  wir wrden sonst evtl. wichtige Bereiche mit Zufallszeug berschreiben:}
  If Not shWriteEx(SqInfo^.SqdFile,0, SqInfo^.SqBase, SizeOf(sqBaseType)) Then
    WriteBase:= UFilesError
   else
    WriteBase:=0;
  End;


Function TMKSquish.StartNewMsg:Boolean; {Initialize msg header}
  Begin
  FillChar(SqInfo^.MsgHdr, SizeOf(SqInfo^.MsgHdr), 0);
  FillChar(SqInfo^.Frame, SizeOf(SqInfo^.Frame), 0);
  StartNewMsg:=InitText=0;
  SqInfo^.StrDateTime:=DateTimeStr(GetDosDate);
  SubjectOfs := 0;
  End;


Function TMKSquish.GetFromName: TString; {Get message from}
  Begin
  GetFromName := Asciiz2Str(SqInfo^.MsgHdr.MsgFrom);
  End;


Function TMKSquish.GetToName: TString; {Get message to}
  Begin
  GetToName := Asciiz2Str(SqInfo^.MsgHdr.MsgTo);
  End;


Function TMKSquish.GetSubj: TString; {Get message subject}
  Begin
  GetSubj := Asciiz2Str(SqInfo^.MsgHdr.Subj);
  End;


Procedure TMKSquish.SetFromName(const Str: OpenString); {Set message from}
  Begin
  Str2Asciiz(Str, SqInfo^.MsgHdr.MsgFrom);
  End;


Procedure TMKSquish.SetToName(const Str: OpenString); {Set message to}
  Begin
  Str2Asciiz(Str,SqInfo^.MsgHdr.MsgTo);
  End;


Procedure TMKSquish.SetSubj(const Str: OpenString); {Set message subject}
  Begin
  Str2Asciiz(Str, SqInfo^.MSgHdr.Subj);
  End;

{$I beginudw.inc}
Function TMKSquish.GetDateTime: TDateTimeStr;
{$IFDEF UseTextDate}
var
  s: TPktDateTimeStr;
  P: PChar;
begin
  SetLength(s, SizeOf(TPktDateTimeStr)-1);
  P:=@(s[1]);
  move(SqInfo^.MsgHdr.AzDate, P^, length(s));
  GetDateTime:=PktDateTimeStrToDateTimeStr(s);
end;
{$ELSE}
Var TmpDate:uint4;
begin
TmpDate:=(SqInfo^.MsgHdr.DateWritten shr 16) +
   ((SqInfo^.MsgHdr.DateWritten and $ffff) shl 16);
GetDateTime:=DateTimeStr(TmpDate);
end;
{$ENDIF}

Function TMKSquish.GetRDateTime: TDateTimeStr;
Var TmpDate:uint4;
begin
TmpDate:=(SqInfo^.MsgHdr.DateArrived shr 16) +
   ((SqInfo^.MsgHdr.DateArrived and $ffff) shl 16);
GetRDateTime:=DateTimeStr(TmpDate);
end;

Function TMKSquish.SqHashName(const Name: OpenString;rcvd:Boolean): uint4;
  Var
    Hash: uint4;
    Tmp: uint4;
    Counter: Word;

  Begin
  Hash := 0;
  Counter := 1;
  While Counter <= Length(Name) Do
    Begin
    Hash := (Hash shl 4) + Ord(DownCase(Name[Counter]));
    Tmp := Hash and $F0000000;
    If (Tmp <> 0) Then
      Hash := (Hash or (Tmp shr 24)) or Tmp;
    Inc(Counter);
    End;
  If Rcvd then
    SqHashName := Hash or $80000000
   else
    SqHashName := Hash and $7fffffff;
  End;
{$I endudw.inc}


Procedure TMKSquish.SetDateTime(const s:TDateTimeStr);
begin
sqInfo^.StrDateTime:=s
end;


Function TMKSquish.GetFromAddr: TFTNAddr;
  Begin
  GetFromAddr := SqInfo^.MsgHdr.Orig;
  End;


Procedure TMKSquish.SetFromAddr(const Addr: TFTNAddr);
  Begin
  SqInfo^.MsgHdr.Orig := Addr;
  End;


Function TMKSquish.GetToAddr: TFTNAddr;
  Begin
  GetToAddr := SqInfo^.MsgHdr.Dest;
  End;


Procedure TMKSquish.SetToAddr(const Addr: TFTNAddr);
  Begin
  SqInfo^.MsgHdr.Dest := Addr;
  End;



Function TMKSquish.ReadFrame(FPos: LongInt):Integer; {Read frame at FPos}
  Begin
  ReadFrame:=ReadVarFrame(SqInfo^.Frame, FPos);
  End;


Function TMKSquish.ReadVarFrame(Var Frame: SqFrameHdrType; FPos: LongInt):Integer; {Read frame at FPos}
  Var
    NumRead: Word;

  Begin
  If Not shReadEx(SqInfo^.SqdFile,FPos, Frame, SizeOf(SqFrameHdrType), NumRead) Then
    ReadVarFrame := UFilesError
   else
    If NumRead<>SizeOf(sqFrameHdrType) then
      ReadVarFrame:=802
     else
      ReadVarFrame:=0
  End;


Function TMKSquish.WriteFrame(FPos: LongInt):Integer; {Write frame at FPos}
Begin
WriteFrame:=WriteVarFrame(SqInfo^.Frame, FPos);
End;


Function TMKSquish.WriteVarFrame(Var Frame: SqFrameHdrType; FPos: LongInt):Integer; {Write frame at FPos}
Begin
  If Not shWriteEx(SqInfo^.SqdFile,FPos, Frame, SizeOf(SqFrameHdrType)) Then
    WriteVarFrame:= UFilesError
   else
    WriteVarFrame:=0;
End;



Function TMKSquish.UnlinkFrame(Var Frame: SqFrameHdrType):Integer;
Var TmpFrame:SqFrameHdrType;
    err:Integer;
Begin
err:=0;
If Frame.PrevFrame <> 0 Then
  Begin
  err:=ReadVarFrame(TmpFrame, Frame.PrevFrame);
  If err=0 then
    begin
    TmpFrame.NextFrame := Frame.NextFrame;
    err:=WriteVarFrame(TmpFrame, Frame.PrevFrame);
    end
  End;
If(err=0)and(Frame.NextFrame<>0) Then
  Begin
  err:=ReadVarFrame(TmpFrame, Frame.NextFrame);
  If Err=0 then
    begin
    TmpFrame.PrevFrame := Frame.PrevFrame;
    err:=WriteVarFrame(TmpFrame, Frame.NextFrame);
    end;
  End;
UnLinkFrame:=err;
End;

Procedure TMKSquish.LoadFree;
  Var
    i: Word;
    TmpFrame: SqFrameHdrType;
    TmpPos: TFileOfs;

  Begin
  SqInfo^.FreeLoaded := false;
  For i := 1 to MaxFree Do
    Begin
    FreeArray^[i].FreePos := 0;
    FreeArray^[i].FreeSize := 0;
    End;
  i := 0;
  TmpPos := SqInfo^.SqBase.FirstFree;
  While ((TmpPos <> 0) and (i < MaxFree)) Do
    Begin
    If ReadVarFrame(TmpFrame, TmpPos)<>0 then
      exit;
    Inc(i);
    FreeArray^[i].FreeSize := TmpFrame.FrameLength;
    FreeArray^[i].FreePos := TmpPos;
    TmpPos := TmpFrame.NextFrame;
    End;
  SqInfo^.HighestFree := i;
  SqInfo^.FreeLoaded := True;
  End;


Function TMKSquish.FindFrame(Var FL: LongInt; Var FramePos: LongInt):Integer;
  Var
    TmpFrame: SqFrameHdrType;
    BestFoundPos: LongInt;
    BestFoundSize: LongInt;
    BestIdx: Word;
    i: Word;

  Begin
  bestidx:=0;
  If Not SqInfo^.FreeLoaded Then
    begin
    LoadFree;
    If Not SqInfo^.FreeLoaded Then
      begin
      FindFrame:=804;
      exit;
      end
    end;
  BestFoundPos := 0;
  BestFoundSize := 0;
  For i := 1 to SqInfo^.HighestFree Do
    Begin
    If (FreeArray^[i].FreeSize > FL) Then
      Begin
      If ((BestFoundSize = 0) or (FreeArray^[i].FreeSize < BestFoundSize)) Then
        Begin
        BestFoundSize := FreeArray^[i].FreeSize;
        BestFoundPos := FreeArray^[i].FreePos;
        BestIdx:=i;
        End;
      End
    End;
  FramePos := BestFoundPos;
  If FramePos <> 0 Then
    Begin
    If(ReadVarFrame(TmpFrame, FramePos)<>0)or(UnLinkFrame(TmpFrame)<>0) then
      begin
      FindFrame:=805;
      exit;
      end;
    FreeArray^[BestIdx].FreePos := 0;
    FreeArray^[BestIdx].FreeSize := 0;
    If TmpFrame.PrevFrame = 0 Then
      SqInfo^.SqBase.FirstFree := TmpFrame.NextFrame;
    If TmpFrame.NextFrame = 0 Then
      SqInfo^.SqBase.LastFree := TmpFrame.PrevFrame;
    FL := TmpFrame.FrameLength;
    FindFrame:=0;
    End
   else
    Begin
    FL := 0;
    FramePos := SqInfo^.SqBase.EndFrame;
    FindFrame:=0;
    End
  End;


Function TMKSquish.KillMsg(MsgNum: LongInt):Integer;
  Var
    i: TIDXNum;
    KillPos: LongInt;
    IndexPos: LongInt;
    KillFrame,TmpFrame: SqFrameHdrType;
    AlreadyLocked: Boolean;
    FreeCtr: Word;
    Error:Integer;

  Begin
  IF MsgNum=-1 then
    begin
    KillMsg:=900;
    {$IFDEF UseErrDlg}
    InternalError($7309);
    {$ENDIF}
    exit;
    end;
  AlreadyLocked := SqInfo^.Locked;
  If Not AlreadyLocked Then
    If LockMsgBase Then;
  If not SQI_OK(SqI) Then
    KillMsg := 999
  Else
    Begin
    If not SQI_FindMsgNum(sqi,MsgNum,i) then
      KillMsg:=870{Nachricht nicht gefunden}
     else
      begin
      IndexPos := i;
      KillPos := SQI_GetOfs(sqI,i);
      If KillPos<0 then
        KillMsg:=997
       else
        begin
        Error:=ReadVarFrame(KillFrame, KillPos);
        If Error<>0 then
          KillMsg:=Error
         else
        If KillFrame.FrameType<>sqFrameMsg then
          KillMsg:=970
         else
          begin
          KillFrame.FrameType := sqFrameRLE;{Markieren als in Vernderung begriffen}
          Error:=WriteVarFrame(KillFrame, KillPos);
          If Error<>0 then
            KillMsg:=Error
           else
            begin
            KillFrame.FrameType := sqFrameFree; {Als gelscht markieren}
            If KillFrame.PrevFrame = 0 Then
              SqInfo^.SqBase.BeginFrame := KillFrame.NextFrame;
            If KillFrame.NextFrame = 0 Then
              SqInfo^.SqBase.LastFrame := KillFrame.PrevFrame;
            Error:=UnLinkFrame(KillFrame);
            If Error<>0 then
              KillMsg:=Error
             else
              begin
              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;
                error:=0;
                End
               else
                Begin
                KillFrame.NextFrame := 0;
                KillFrame.PrevFrame := SqInfo^.SqBase.LastFree;
                Error:=ReadVarFrame(TmpFrame, SqInfo^.SqBase.LastFree);
                If Error=0 then
                  begin
                  TmpFrame.NextFrame := KillPos;
                  Error:=WriteVarFrame(TmpFrame, SqInfo^.SqBase.LastFree);
                  If Error=0 then
                    SqInfo^.SqBase.LastFree := KillPos;
                  end
                End;
              If Error=0 then
                Error:=WriteVarFrame(KillFrame, KillPos);
              If Error<>0 then
                KillMsg:=Error
               else
                begin
                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);
                SQI_Delete(sqI,IndexPos);
                KillMsg:=0;
                end
              end
            end
          end
        end
      end
    End;
  If Not AlreadyLocked Then
    If UnlockMsgBase Then;
  End;


Function TMKSquish.ReadMsgHdr(FPos: LongInt):Integer; {Read msg hdr for frame at FPos}
  Var
    NumRead: Word;
  Begin
  If shReadEx(SqInfo^.SqdFile,FPos+sqInfo^.sqBase.sqFrameHdrSize, SqInfo^.MsgHdr, SizeOf(SqMsgHdrType), NumRead) Then
    If SizeOf(SqMsgHdrType)=NumRead then {Alles gelesen}
      ReadMsgHdr:=0
     else
      ReadMsgHdr:=971 {MsgHdr konnte aus irgendeinem Grund nicht vollstndig gelesen werden}
   else
    ReadMsgHdr := UFilesError;
  End;


Function TMKSquish.WriteMsgHdr(FPos: LongInt):Integer; {Read msg hdr for frame at FPos}
Begin
If Not shWriteEx(SqInfo^.SqdFile,FPos + sqInfo^.sqBase.SqFrameHdrSize,
                          SqInfo^.MsgHdr, SizeOf(SqMsgHdrType)) Then
  WriteMsgHdr:=0
 else
  WriteMsgHdr:=UFilesError;
End;

Function TMKSquish.WriteText(FPos: LongInt):Integer; {Write text buffer for frame at Fpos}
var g,s:Word;add,h:PsqAddMsgText;
Begin
WriteText:=0;
g:=sqInfo^.TxtCtr;
If g=0 then
  begin
  WriteText:=9345;
  exit;
  end;
If sqInfo^.MsgText=nil then
  begin
  WriteText:=9346;
  exit;
  end;
If g>sqStdTxtBufferSize then
   s:=sqStdTxtBufferSize
  else
   s:=sqInfo^.TxtCtr;
If not shWriteEx(SqInfo^.SqdFile,FPos +
         sqInfo^.sqBase.SqFrameHdrSize +
         sqMsgHdrSize,
         SqInfo^.MsgText^.data, s) Then
  begin
  WriteText:=UFilesError;
  exit;
  end;
dec(g,s);
add:=sqInfo^.MsgText^.add;
dispose(sqinfo^.msgText);
sqInfo^.MsgText:=nil;
While assigned(add) do
      begin
      If g>sqAddTxtBufferSize then
         s:=sqAddTxtBufferSize
        else
         s:=g;
      If not shWriteEx(SqInfo^.SqdFile,FPos +
         sqInfo^.sqBase.SqFrameHdrSize +
         sqMsgHdrSize,
         SqInfo^.MsgText^.data, s) Then
             begin
             WriteText:=UFilesError;
             exit;
             end;
      dec(g,s);
      h:=add^.next;
      dispose(add);
      add:=h;
      end;
End;

Function TMKSquish.ReadText(FPos: LongInt):Integer;
var e:Integer;l:Longint;p:PsqAddMsgText;
  Begin
  discardText;
  Seek(SqInfo^.SqdFile, FPos + sqInfo^.sqBase.SqFrameHdrSize + sqMsgHdrSize);
  e:= IoResult;
  If e = 0 Then
    Begin
    {berprfung auf Speicher sollte etwas weiter runter:}
    If(SqInfo^.Frame.MsgLength<sqMsgHdrSize)
      or(SqInfo^.Frame.MsgLength-sqMsgHdrSize>MaxAvail) then
      begin
      ReadText:=989;
      sqInfo^.MsgText:=nil;
      SqInfo^.TxtCtr := 1;
      SqInfo^.MsgDone := true;
      exit;
      end;
    l:=SqInfo^.Frame.MsgLength-sqMsgHdrSize;
    new(sqInfo^.MsgText);
    If l<=sqStdTxtBufferSize then
       begin
       sqInfo^.msgText^.add:=nil;
       BlockRead(SqInfo^.SqdFile, SqInfo^.MsgText^.data,l);
       end
      else
       begin
       BlockRead(SqInfo^.SqdFile, SqInfo^.MsgText^.data,sqStdTxtBufferSize);
       dec(l,sqStdTxtBufferSize);
       new(sqInfo^.msgText^.add);
       p:=sqInfo^.msgText^.add;
       While l>sqAddTxtBufferSize do
         begin
         BlockRead(SqInfo^.SqdFile, SqInfo^.MsgText^.data,sqAddTxtBufferSize);
         new(p^.next);
         p:=p^.next;
         dec(l,sqAddTxtBufferSize);
         end;
       BlockRead(SqInfo^.SqdFile, SqInfo^.MsgText^.data,l);
       p^.next:=nil;
       end;
    ReadText := IoResult;
    End
   else
    ReadText:=e;
  SqInfo^.TxtCtr := 1;
  SqInfo^.MsgDone := False;
  LastSoft := False;
  End;

Procedure TMKSquish.DiscardText;
var h:PsqAddMsgText;
begin
  If assigned(SqInfo^.MsgText) then
    begin
    With sqInfo^.MsgText^ do
       While assigned(add) do
          begin
          h:=add^.next;
          dispose(add);
          add:=h;
          end;
    dispose(sqInfo^.MsgText);
    SqInfo^.MsgText:=nil;
    end;
end;

Function TMKSquish.InitText:Integer;
  Begin
  SqInfo^.TxtCtr := 0;
  SqInfo^.Inctrl:=0;
  discardText;
  If MaxAvail<sizeof(TsqMsgText) then
    InitText:=999
   else
    begin
    new(sqInfo^.MsgText);
    If sqInfo^.MsgText=nil then
      InitText:=998
     else
      begin
      sqInfo^.msgText^.add:=nil;
      InitText:=0
      end
    end;
  End;


Procedure TMKSquish.DoString(const Str: OpenString); {Add string to message text}
  Var
    i: Word;
  Begin
  {(B}
  If sqInfo^.InCtrl=3 then {Bisher waren Kludges}
    begin
    DoChar(#0);
    sqInfo^.Frame.ControlLength:=sqInfo^.TxtCtr;
    sqInfo^.InCtrl:=0;
    end;
  {B)}
  i := 1;
  While i <= Length(Str) Do
    Begin
    DoChar(Str[i]);
    Inc(i);
    End;
  End;
{(B}
Procedure TMKSquish.DoKludgeLn(const Str:OpenString);
var i: Word;
begin
If(SqInfo^.TxtCtr=0)or(sqInfo^.InCtrl=3) then
   begin
    i := 1;
    While i <= Length(Str) Do
      Begin
      DoChar(Str[i]);
      Inc(i);
      End;
   sqInfo^.Frame.ControlLength:=sqInfo^.TxtCtr;
   sqInfo^.InCtrl:=3;
   end
  else
   DoStringLn(Str);
end;
{B)}
Procedure TMKSquish.DoChar(Ch: Char); {Add character to message text}
var l:Longint;p:PsqAddMsgText;
  Begin
  If SqInfo^.TxtCtr < SqStdTxtBufferSize Then
    Begin
    Inc(SqInfo^.TxtCtr);
    SqInfo^.MsgText^.data[SqInfo^.TxtCtr] := ch;
    End
   else
    begin
    l:=sqInfo^.TxtCtr-sqStdTxtBufferSize;
    p:=sqInfo^.MsgText^.add;
    While l >= sqAddTxtBufferSize do
        begin
        dec(l,sqAddTxtBufferSize);
        If l>0 then
          p:=p^.next;
        end;
    If l=0 then
      begin
      new(p^.next);
      p:=p^.next;
      p^.next:=nil;
      end;
    inc(sqInfo^.TxtCtr);
    p^.data[l+1]:=ch
    end
  End;

Procedure TMKSquish.DoStringLn(const Str: OpenString); {Add string and newline to msg text}
  Begin
  DoString(Str);
  DoChar(#13);
  End;

Function TMKSquish.KillExcess:Integer;
  Var
    AlreadyLocked: Boolean;
    Error:Integer;

  Begin
  AlreadyLocked := SqInfo^.Locked;
  If Not AlreadyLocked Then
    If LockMsgBase Then;
  If not SQI_OK(SqI) Then
    KillExcess := 999
  Else
    Begin
    If ((SqInfo^.SqBase.MaxMsg > 0) and
    (SqInfo^.SqBase.MaxMsg > SqInfo^.SqBase.SkipMsg)) Then
      Begin
      While (SqInfo^.SqBase.NumMsg > SqInfo^.SqBase.MaxMsg) Do
        begin
        Error:=KillMsg(SQI_GetUMID(sqI,SqInfo^.SqBase.SkipMsg + 1));
        If Error<>0 then
          begin
          KillExcess:=Error;
          If Not AlreadyLocked Then
            If UnlockMsgBase Then;
          exit
          end
        end;
      End;
    KillExcess:=0;
    End;
  If Not AlreadyLocked Then
    If UnlockMsgBase Then;
  End;


Function TMKSquish.WriteMsg: Word; {Write msg to msg base}
  Var
    MsgSize: LongInt;
    FrameSize: LongInt;
    FramePos: LongInt;
    TmpFrame,LastFrame: SqFrameHdrType;
    TmpDate: LongInt;
    AlreadyLocked: Boolean;
    Error:Integer;

  Begin
  {(B}
  If sqInfo^.InCtrl=3 then {Mssen Kludges abgeschlossen werden?}
    begin
    DoChar(#0);
    sqInfo^.InCtrl:=0;
    SqInfo^.Frame.ControlLength:=SqInfo^.TxtCtr;
    end;
  {B)}
  DoChar(#0);{MsgText abschlieen}
  TmpDate:=PackDateTime(sqInfo^.StrDateTime);
  SqInfo^.MsgHdr.DateWritten :=  (TmpDate shr 16) + ((TmpDate and $ffff) shl 16);
  TmpDate := GetDosDate;
  SqInfo^.MsgHdr.DateArrived := (TmpDate shr 16) + ((TmpDate and $ffff) shl 16);
  Str2Asciiz({TmpStr}CutForPKT(sqInfo^.strDateTime), {20,} SqInfo^.MsgHdr.AZDate);
  AlreadyLocked := SqInfo^.Locked;
  If Not AlreadyLocked Then
    If LockMsgBase Then;
  If SqInfo^.Locked Then
    Begin
    If not SQI_OK(SqI) or not SQI_Space(sqi) Then{sqi vorhanden und Platz da?}
      WriteMsg := 999
    Else
      Begin
      MsgSize := SqInfo^.TxtCtr + sqMsgHdrSize;
      FrameSize := MsgSize;
      Error:=FindFrame(FrameSize, FramePos);
      If Error=0 then
      {Nun hngen wir uns hinten in die Kette ein:}
        If SqInfo^.SqBase.LastFrame <> 0 Then
          begin
          Error:=ReadVarFrame(LastFrame, SqInfo^.SqBase.LastFrame);
          LastFrame.NextFrame := FramePos;
          TmpFrame.PrevFrame := SqInfo^.SqBase.LastFrame;
          {schreiben vorsichtshalber erst hinterher}
          end
         else
          TmpFrame.PrevFrame := 0;
      If Error<>0 then {Falls Fehler aufgetreten}
        begin
        WriteMsg:=Error;
        If Not AlreadyLocked Then
          If UnLockMsgBase Then;
        exit {Schnell raus...}
        end;
      TmpFrame.FrameLength := FrameSize;
      {Den Rahmen fr unsere Nachricht an uns anpassen:}
      TmpFrame.Id := SqHdrId;
      TmpFrame.FrameType := SqFrameMsg;
      TmpFrame.NextFrame := 0;
      TmpFrame.MsgLength := MsgSize;
      TmpFrame.ControlLength := sqInfo^.Frame.ControlLength;
      {Falls es sich um einen neuen Rahmen handelt, uns den Platz reservieren:}
      If TmpFrame.FrameLength = 0 Then
        Begin {neuer Frame am Ende der Datei:}
        TmpFrame.FrameLength := TmpFrame.MsgLength + 0; {slack to minimize free frames}
        {Nun noch das Ende der Datei nach hinten verschieben:}
        SqInfo^.SqBase.EndFrame := FramePos + SqInfo^.sqBase.sqFrameHdrSize + TmpFrame.FrameLength;
        End;
      {Dann die Daten schreiben:}
      Error:=WriteVarFrame(TmpFrame, FramePos);
      If Error=0 then
        begin
        If sqInfo^.sqBase.sqFrameHdrSize>sqStdFrameHdrSize then
          if not shWriteZeroEx(SqInfo^.SqdFile,
             FramePos+sqStdFrameHdrSize,
             sqInfo^.sqBase.sqFrameHdrSize-sqStdFrameHdrSize) then
          Error:=UFilesError;
        If Error=0 then
          Error:=WriteMsgHdr(FramePos);
        If Error=0 then
          Error:=WriteText(FramePos);
        end;
      If Error<>0 then {Falls Fehler aufgetreten}
        begin
        WriteMsg:=Error;
        If Not AlreadyLocked Then
          If UnLockMsgBase Then;
        exit {Schnell raus...}
        {Wenn wir hier rausgehen, ist nur der Frame nutzlos ausgelinkt und
        verbraucht unntig Platz, die vorhandenen Strukturen mssten aber
        ohne Schden sein}
        end;
      {Nun tragen verlinken wir den Frame, damit er in der SQD drinnen ist:}
      If SqInfo^.SqBase.LastFrame <> 0 Then
        Error:=WriteVarFrame(LastFrame, SqInfo^.SqBase.LastFrame)
       else
        SqInfo^.SqBase.BeginFrame := FramePos;
      SqInfo^.SqBase.LastFrame := FramePos;
      Inc(SqInfo^.SqBase.NumMsg);
      Inc(SqInfo^.SqBase.UId);
      SqInfo^.SqBase.HighMsg := SqInfo^.SqBase.NumMsg;
      {Nun den Rahmen in den Index eintragen:}
      If SQI_Add(sqi,FramePos,
                    SqInfo^.SqBase.UID-1,
                    SqHashName(
                      Asciiz2Str(SqInfo^.MsgHdr.MsgTo), {To-Feld}
                      (SqInfo^.MsgHdr.Attr and SqMsgRcvd) <> 0)) then
        begin
        If KillExcess<>0 then;{berschssige Nachrichten lschen}
        SqInfo^.CurrIdx := SqInfo^.SqBase.NumMsg;
        WriteMsg := 0;
        end
       else
        begin
        WriteMsg:=990;{Darf gar nicht auftreten, da SQI_Space schon true lieferte}
        {$IFDEF UseErrDlg}
        InternalError($7310)
        {$ENDIF}
        end;
      End;
    If Not AlreadyLocked Then
      If UnLockMsgBase Then;
    End
  Else
    WriteMsg := 5;
  End;

Function TMKSquish.GetString(MaxLen: Word): TString;
  Var
    WPos: Word;
    WLen: TSIndex;
    CurrLen: Word;
    StrDone: Boolean;
    StartSoft: Boolean;
    TmpCh: Char;
    {$IFDEF NoJam}
    TempStr:TString;
    {$ENDIF}
  Begin
  {$IFDEF NoJam}
  TempStr:='';
  {$ENDIF}
  CurrLen := 0;
  StrDone := False;
  {PPos := SqInfo^.TxtCtr;}
  WPos := 0;
  WLen := 0;
  StartSoft := LastSoft;
  LastSoft := True;
  TmpCh := GetChar;
  If StartSoft and(TmpCh=#$0d)then
    TmpCh:=GetChar;
  While ((Not StrDone) And (CurrLen < MaxLen) And (Not SqInfo^.MsgDone)) Do Begin
    Case TmpCh of
      #$00:;
      #$0d: Begin
            StrDone := True;
            LastSoft := False;
            End;
      #$8d:;
      #$0a:;
      #$20: Begin
            If ((CurrLen <> 0) or (Not StartSoft)) Then
              Begin
              Inc(CurrLen);
              WLen := CurrLen;
              {$IFDef NoJam}
              TempStr:=TempStr+TmpCh;
              {$ELSE}
              GetString[CurrLen] := TmpCh;
              {$ENDIF}
              WPos := SqInfo^.TxtCtr;
              End
            Else
              StartSoft := False;
            End;
      Else
        Begin
        Inc(CurrLen);
        {$IFDef NoJam}
        TempStr:=TempStr+TmpCh;
        {$ELSE}
        GetString[CurrLen] := TmpCh;
        {$ENDIF}
        End;
      End;
    If Not StrDone Then
      TmpCh := GetChar;
    End;
  If StrDone Then
    Begin
    {$IFDef NoJam}
    GetString:=TempStr;
    {$ELSE}
    GetString[0] := Chr(CurrLen);
    {$ENDIF}
    End
  Else
    If SqInfo^.MsgDone Then
      Begin
      {$IFDef NoJam}
      GetString:=TempStr;
      {$ELSE}
      GetString[0] := Chr(CurrLen);
      {$ENDIF}
      End
    Else
      Begin
      If WLen = 0 Then
        Begin
        {$IFDef NoJam}
        GetString:=TempStr;
        {$ELSE}
        GetString[0] := Chr(CurrLen);
        {$ENDIF}
        Dec(SqInfo^.TxtCtr);
        End
      Else
        Begin
        {$IFDef NoJam}
        GetString:=Copy(TempStr,1,WLen);
        {$ELSE}
        GetString[0] := Chr(WLen);
        {$ENDIF}
        SqInfo^.TxtCtr := WPos;
        End;
      End;
    GetNextChar; {now EOM is correctly set}
  End;


Function TMKSquish.EOM: Boolean;
  Begin
  EOM := SqInfo^.MsgDone;
  End;

Function TMKSquish.GetChar: Char;
begin
  GetChar:=GetNextChar;
  AheadLooked:=false;
end;

Function TMkSquish.GetNextChar: Char;
var
  c:Char;
  l:Longint;
  p:PsqAddMsgText;
Begin
 if AheadLooked then begin
  GetNextChar:=AheadChar;
 end else if EndOfControlRegionReached then begin
   EndOfControlRegionReached:=false; {next we are reading real message-text!, TxtCtr already points to it}
   AHeadChar:=#13;
   GetNextChar:=AHeadChar;
 end else begin
  With sqInfo^ do begin
    If TxtCtr>sqStdTxtBufferSize then begin
      p:=MsgText^.add;
      l:=TxtCtr-sqStdTxtBufferSize;
      While (l>sqAddTxtBufferSize) do begin
        p:=p^.next;
        dec(l,sqAddTxtBufferSize);
      end;
      c:=p^.data[l];
    end else begin
      c:=MsgText^.data[TxtCtr];
    end;
  end;
  If SqInfo^.TxtCtr<=SqInfo^.Frame.ControlLength then begin
    EndOfControlRegionReached:=(SqInfo^.TxtCtr=SqInfo^.Frame.ControlLength);
    IF (c=#0) Then begin
      AheadChar := #13;
      Inc(SqInfo^.TxtCtr);
    End else begin
      If (SqInfo^.TxtCtr<>1)and(c=#1)and(sqInfo^.InCtrl=1) Then begin
        AheadChar:=#13;
        sqInfo^.InCtrl:=2; {Damit das beim nchsten mal #1 durchkommt}
      end else Begin
        AheadChar := c;
        Inc(SqInfo^.TxtCtr);
        sqInfo^.InCtrl:=1;
      End;
    end;
    EndOfControlRegionReached:=EndOfControlRegionReached and not (AheadChar=#13); {perhaps there could be such a CR at the end of the controlinfo, so no addional one is needed}
  end else begin
    If ((SqInfo^.TxtCtr >= SqInfo^.Frame.MsgLength) Or (c = #0)) Then Begin
      AheadChar := #0;
      SqInfo^.MsgDone := True;
    End Else Begin
      AheadChar := c;
      Inc(SqInfo^.TxtCtr);
    End;
  End;
  AheadLooked:=True;
  GetNextChar:=AheadChar;
 end;
End;

Function TMKSquish.SeekFirst(MsgNum:LongInt;Reload:Boolean):Boolean;
  Begin
  discardText;
  If Reload then
    If SQI_ReAssign(sqi,NumberOfMsgs)<>0 then
      begin
      Seekfirst:=false;
      exit
      end;
  SeekFirst:=SQI_FindMsgNum(sqI,MsgNum,SqInfo^.currIdx);
  End;

Function TMKSquish.SeekFirstAfter(MsgNum:LongInt;Reload:Boolean):Boolean;
  Begin
  discardText;
  If Reload then
    If SQI_ReAssign(sqi,NumberOfMsgs)<>0 then
      begin
      SeekfirstAfter:=false;
      exit
      end;
  SeekFirstAfter:=SQI_FindAfterMsgNum(sqI,MsgNum,SqInfo^.currIdx);
  End;


Function TMKSquish.GetMsgNum: TUMID;
Begin
GetMsgNum:=SQI_GetUMID(SqI,sqInfo^.CurrIdx);
End;

Function TMKSquish.SeekNext:Boolean;
Begin
Inc(SqInfo^.CurrIdx);
SeekNext:=sqInfo^.currIdx<=sqi^.MsgCount;
End;

Function TMKSquish.SeekPrior:Boolean;
Begin
If sqInfo^.CurrIdx>0 then
  dec(SqInfo^.CurrIdx);
SeekPrior:=sqInfo^.CurrIdx>0
End;


Function TMKSquish.GetIdxFramePos: LongInt;
  Begin
  GetidxFramePos:=SQI_GetOfs(sqI,SqInfo^.CurrIdx);
  End;


Function TMKSquish.IsLocal: Boolean; {Is current msg local}
  Begin
  IsLocal := ((SqInfo^.MsgHdr.Attr and SqMsgLocal) <> 0);
  End;


Function TMKSquish.IsCrash: Boolean; {Is current msg crash}
  Begin
  IsCrash := ((SqInfo^.MsgHdr.Attr and SqMsgCrash) <> 0);
  End;


Function TMKSquish.IsKillSent: Boolean; {Is current msg kill sent}
  Begin
  IsKillSent := ((SqInfo^.MsgHdr.Attr and SqMsgKill) <> 0);
  End;


Function TMKSquish.IsSent: Boolean; {Is current msg sent}
  Begin
  IsSent := ((SqInfo^.MsgHdr.Attr and SqMsgSent) <> 0);
  End;


Function TMKSquish.IsFAttach: Boolean; {Is current msg file attach}
  Begin
  IsFAttach := ((SqInfo^.MsgHdr.Attr and SqMsgFile) <> 0);
  End;


Function TMKSquish.IsReqRct: Boolean; {Is current msg request receipt}
  Begin
  IsReqRct := ((SqInfo^.MsgHdr.Attr and SqMsgRRQ) <> 0);
  End;


Function TMKSquish.IsReqAud: Boolean; {Is current msg request audit}
  Begin
  IsReqAud := ((SqInfo^.MsgHdr.Attr and SqMsgArq) <> 0);
  End;


Function TMKSquish.IsRetRct: Boolean; {Is current msg a return receipt}
  Begin
  IsRetRct := ((SqInfo^.MsgHdr.Attr and SqMsgCpt) <> 0);
  End;


Function TMKSquish.IsFileReq: Boolean; {Is current msg a file request}
  Begin
  IsFileReq := ((SqInfo^.MsgHdr.Attr and SqMsgFreq) <> 0);
  End;


Function TMKSquish.IsRcvd: Boolean; {Is current msg received}
  Begin
  IsRcvd := ((SqInfo^.MsgHdr.Attr and SqMsgRcvd) <> 0);
  End;


Function TMKSquish.IsPriv: Boolean; {Is current msg priviledged/private}
  Begin
  IsPriv := ((SqInfo^.MsgHdr.Attr and SqMsgPriv) <> 0);
  End;


Function TMKSquish.IsEchoed: Boolean;
  Begin
  IsEchoed := ((SqInfo^.MsgHdr.Attr and SqMsgScanned) = 0);
  End;

Function TMKSquish.IsHold: Boolean;
  Begin
  IsHold := ((SqInfo^.MsgHdr.Attr and SqMsgHold) <> 0);
  End;

Function TMKSquish.IsDeleted: Boolean; {Is current msg deleted}
  Begin
  IsDeleted := False;
  End;


Function TMKSquish.GetRefer: LongInt; {Get reply to of current msg}
  Begin
  GetRefer := LongInt(SqInfo^.MsgHdr.ReplyTo);
  End;


Procedure TMKSquish.SetRefer(Num: LongInt); {Set reply to of current msg}
  Begin
  SqInfo^.MsgHdr.ReplyTo := LongInt(Num);
  End;


Procedure TMKSquish.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 TMKSquish.SetFwd(St: Boolean);
  Begin
  SetAttr(St, SqMsgFwd);
  End;


Function TMKSquish.IsFwd: Boolean;
  Begin
  IsFwd := ((SqInfo^.MsgHdr.Attr and SqMsgFwd) <> 0);
  End;


Procedure TMKSquish.SetLocal(St: Boolean); {Set local status}
  Begin
  SetAttr(St, SqMsgLocal);
  End;

Procedure TMKSquish.SetHold(St: Boolean); {Set local status}
  Begin
  SetAttr(St, SqMsgHold);
  End;

Procedure TMKSquish.SetRcvd(St: Boolean); {Set received status}
  Begin
  SetAttr(St, SqMsgRcvd);
  End;


Procedure TMKSquish.SetPriv(St: Boolean); {Set priveledge vs public status}
  Begin
  SetAttr(St, SqMsgPriv);
  End;


Procedure TMKSquish.SetEcho(ES: Boolean);
  Begin
  SetAttr(Not ES, SqMsgScanned);
  End;


Procedure TMKSquish.SetCrash(St: Boolean); {Set crash netmail status}
  Begin
  SetAttr(St, SqMsgCrash);
  End;


Procedure TMKSquish.SetKillSent(St: Boolean); {Set kill/sent netmail status}
  Begin
  SetAttr(St, SqMsgKill);
  End;


Procedure TMKSquish.SetSent(St: Boolean); {Set sent netmail status}
  Begin
  SetAttr(St, SqMsgSent);
  End;


Procedure TMKSquish.SetFAttach(St: Boolean); {Set file attach status}
  Begin
  SetAttr(St, SqMsgFile);
  End;


Procedure TMKSquish.SetReqRct(St: Boolean); {Set request receipt status}
  Begin
  SetAttr(St, SqMsgRrq);
  End;


Procedure TMKSquish.SetReqAud(St: Boolean); {Set request audit status}
  Begin
  SetAttr(St, SqMsgarq);
  End;


Procedure TMKSquish.SetRetRct(St: Boolean); {Set return receipt status}
  Begin
  SetAttr(St, SqMsgCpt);
  End;


Procedure TMKSquish.SetFileReq(St: Boolean); {Set file request status}
  Begin
  SetAttr(St, SqMsgFreq);
  End;


Function TMKSquish.MsgStartUp:Boolean;
  Begin
  MsgStartUp:=false;
  SqInfo^.CurrentUID := SQI_GetUMID(sqI,SqInfo^.CurrIdx);
  If sqInfo^.CurrentUID<0 then
    exit;
  SqInfo^.CurrentFramePos := GetIdxFramePos;
  If(ReadFrame(SqInfo^.CurrentFramePos)<>0)or(sqInfo^.Frame.Id<>SqHdrId)
     or(sqInfo^.Frame.FrameType<>SqFrameMsg) then
    Exit;
  If ReadMsgHdr(SqInfo^.CurrentFramePos)<>0 then
    exit;
  SubjectOfs:=0;
  MsgStartUp:=true;
  End;


Function TMKSquish.MsgTxtStartUp:Boolean;
Begin
  MsgTxtStartUp:=ReadText(SqInfo^.CurrentFramePos)=0;
  AheadLooked:=false;
  EndOfControlRegionReached:=false;
End;


Function TMKSquish.ReWriteHdr: Word;
  Var
    AlreadyLocked : Boolean;
     Error        : Word;
  Begin
  AlreadyLocked := SqInfo^.Locked;
  If Not AlreadyLocked Then begin
    {$IFDEF UseErrDlg}
    InternalError($7381);
    {$ENDIF}
    {Rewrite sollte nur nach Lock und MsgStartUp verwendet werden, sonst
    knnte ein anderes Programm in der Zwischenzeit nderungen vornehmen}
    If LockMsgBase Then;
  end;
  Error:=Word(WriteFrame(SqInfo^.CurrentFramePos));
  If Error=0 then begin
    Error:=Word(WriteMsgHdr(SqInfo^.CurrentFramePos));
    If Error=0 then begin
         SQI_Set(sqI,sqInfo^.CurrIdx,
            sqInfo^.CurrentFramePos,
            sqInfo^.CurrentUID,
            SqHashName(SqInfo^.MsgHdr.MsgTo,IsRcvd));
    end;
    If Not AlreadyLocked Then
      If UnLockMsgBase Then;
  end;
  ReWriteHdr:=Error;
End;


Function TMKSquish.NumberOfMsgs: LongInt;
  Var
    TmpBase: SqBaseType;

  Begin
  If SqInfo^.sqdOpened then
    NumberOfMsgs:=sqInfo^.sqBase.NumMsg
   else
    If LoadFile(SqInfo^.FN + '.sqd', TmpBase, SizeOf(TmpBase)) = 0 Then
      NumberOfMsgs := TmpBase.NumMsg
     Else
      NumberOfMsgs := 0;
  End;


Function TMKSquish.GetLastRead(UNum: LongInt): LongInt;
  Var
    LRec: LongInt;

  Begin
  If ((UNum + 1) * SizeOf(LRec)) >
  SizeFile(SqInfo^.FN + '.sql') Then
    GetLastRead := 0
  Else
    Begin
    If LoadFilePos(SqInfo^.FN + '.sql', LRec, SizeOf(LRec),
    UNum * SizeOf(LRec)) = 0 Then
      GetLastRead := LRec
    Else
      GetLastRead := 0;
    End;
  End;


Procedure TMKSquish.SetLastRead(UNum: LongInt; LR: LongInt);
  Var
    LRec: LongInt;
    Status: Word;

  Begin
  Status := 0;
  If ((UNum + 1) * SizeOf(LRec)) >
  SizeFile(SqInfo^.FN + '.sql') Then
    Begin
    Status := ExtendFile(SqInfo^.FN + '.sql', (UNum + 1) * SizeOf(LRec));
    End;
  LRec := LR;
  If Status = 0 Then
    Status := SaveFilePos(SqInfo^.FN + '.sql', LRec, SizeOf(LRec),
      UNum * SizeOf(LRec));
  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
  {sqBase.uid is the next surely free msg, sqBase.HigMsg is the highest internal number,
  but this call intended to get the highest msgnum, which means for squish the highest
  umsgid:}
  GetHighMsgNum := SQI_GetHighest(sqi);
  End;

(* only stubs, because these functions have to be implemented by this object *)

Procedure sqMsgObj.SetMsgBaseType(dummy:TMailType);
begin
end;

Function sqMsgObj.GetTxtPos: LongInt;
Begin
   GetTxtPos:=-1;
End;

Procedure sqMsgObj.SetTxtPos(TP: LongInt);
  Begin
  End;

End.
