(*
**	JAM(mbp) - The Joaquim-Andrew-Mats Message Base Proposal
**
**	Turbo Pascal API
**
**	Written by Joaquim Homrighausen.
**
**	----------------------------------------------------------------------
**
**	jammb.pas (JAMmb)
**
**	Prototypes and definitions for the JAM message base API
**
**	Copyright 1993 Joaquim Homrighausen, Andrew Milner, Mats Birch, and
**	Mats Wallin. ALL RIGHTS RESERVED.
**
**	93-06-28	JoHo
**	Initial coding
*)
UNIT JAMMB;

{$IFNDEF VER55}
	{$A-,B-,E-,D+,L+,F-,I-,N-,O+,R-,S-,V-,X+}
{$ELSE}
	{$A-,B-,E-,D+,L+,F-,I-,N-,O+,R-,S-,V-}
{$ENDIF}

INTERFACE

USES
	JAM;

{-API status codes}

CONST
	JAMAPIMSG_NOTHING				=	0;
	JAMAPIMSG_HDRPRINT			=	1;   {Header fingerprint missing or invalid}
	JAMAPIMSG_HDRPASSWORD		=	2;   {Invalid password for header file}
	JAMAPIMSG_MSGPASSWORD		=	3;   {Invalid password for message}
	JAMAPIMSG_ISOPEN				=	4;   {Message base is open}
	JAMAPIMSG_ISNOTOPEN			=	5;   {Message base is not open}
	JAMAPIMSG_ISNOTLOCKED		=	6;   {Message base is not locked}
	JAMAPIMSG_SEEKERROR			=	7;   {Unable to seek in file}
	JAMAPIMSG_CANTMKFILE		=	8;   {Unable to create file}
	JAMAPIMSG_CANTRDFILE		=	9;   {Unable to read file}
	JAMAPIMSG_CANTWRFILE		=	10;  {Unable to write file}
	JAMAPIMSG_CANTRMFILE		=	11;  {Unable to remove file}
	JAMAPIMSG_FIRSTMSG			=	12;  {Already on first message}
	JAMAPIMSG_NEWMODCOUNTER	=	13;  {ModCounter have been updated since read}
	JAMAPIMSG_NOMORETEXT		=	14;  {Read to end of message text}
	JAMAPIMSG_CANTLKFILE		=	15;  {Unable to lock message base}
	JAMAPIMSG_CANTFINDUSER	=	16;  {Unable to locate LastRead record}
	JAMAPIMSG_CANTFINDMSG		=	17;  {Unable to locate message to user}
	JAMAPIMSG_NOMOREMSGS		=	18;  {At end or beginning of file}
	JAMAPIMSG_BADHEADERSIG	=	19;  {Invalid message header signature}
	JAMAPIMSG_BADHEADERREV	= 20;  {Invalid message header revision}
	JAMAPIMSG_INVMSGNUM			=	21;  {Invalid message number specified}

{-Values for FROMWHERE parameter to SeekFunc, indentical to those normally
	defined in most C compiler's IO.H as SEEK_SET, SEEK_CUR, and SEEK_END}

	JAMSEEK_SET							=	0;   {To specified position}
	JAMSEEK_CUR							=	1;   {From current position}
	JAMSEEK_END							=	2;   {From end of file}

{-Return values for user comparison functions}

	ScanMsgHdrStop					=	0;
	ScanMsgHdrDiscard				=	1;
	ScanMsgHdrNextHdr				=	2;

	ScanMsgIdxStop					=	0;
	ScanMsgIdxNextMsg				=	1;

{-Basic object definition and user comparison definitions}

TYPE
	JAMAPIPTR			=	^JAMAPI;

	ScanMsgHdrFunc=	FUNCTION(TheAPI:JAMAPIPTR):INTEGER;
	ScanMsgIdxFunc=	FUNCTION(TheAPI:JAMAPIPTR):INTEGER;

	JAMAPI				=	OBJECT
		BaseName		:	FILENAMETYPE;        {Path for message base}
		WorkBuf			:	JAMBUFPTR;           {Workspace, initialized by user}
		WorkLen			:	LONGINT;             {Length of workspace (above)}
		WorkPos			:	LONGINT;             {Current position, for multi-reads}
		IsOpen			:	BOOLEAN;             {Message base is open}
		HaveLock		:	BOOLEAN;             {.JHR file is locked}
		Errno				:	INTEGER;             {Last DOS error in case of error}
		APImsg			:	INTEGER;             {API status message}
		HdrHandle		:	INTEGER;             {File handle for .JHR file}
		TxtHandle		:	INTEGER;             {File handle for .JDT file}
		IdxHandle		:	INTEGER;             {File handle for .JDX file}
		LrdHandle		:	INTEGER;             {File handle for .JLR file}
		LastMsgNum	:	LONGINT;             {Last message number fetched}
		Idx					:	JAMIDXREC;           {Message index record}
		Hdr					:	JAMHDR;              {Message header record}
		HdrInfo			:	JAMHDRINFO;          {Message header info record}
		SubFieldPtr	:	JAMSUBFIELDPTR;      {Subfield record}
		LastLRDnum	:	LONGINT;             {Last LastRead record read (0-based)}
		LastRead		:	JAMLREAD;            {LastRead record}

		constructor Init;
		destructor Done; VIRTUAL;

		{-DOS file access functions, defined as VIRTUAL to be overriden by an
			object that inherits this object if you feel that these are not
			sufficient.}

		function CreateFile(FileName:FILENAMETYPE):INTEGER; virtual;
		function OpenFile(FileName:FILENAMETYPE):INTEGER; virtual;
		function CloseFile(Handle:INTEGER):INTEGER; virtual;
		function UnlinkFile(FileName:FILENAMETYPE):INTEGER; virtual;
		function LockFile(DoLock:BOOLEAN):INTEGER; virtual;
		function SeekFile(Handle:INTEGER; FromWhere:BYTE; FilePos:LONGINT):LONGINT; virtual;
		function ReadFile(Handle:INTEGER; VAR Buffer; DatLen:WORD):WORD; virtual;
		function WriteFile(Handle:INTEGER; VAR Buffer; DatLen:WORD):WORD; virtual;

		{-Function to convert uppercase to lowercase}

		procedure LowCaseBuf(var Buf; DatLen:WORD);

		{-JAM message base access functions}

		function OpenMB:BOOLEAN;
		function CreateMB:BOOLEAN;
		function CloseMB:BOOLEAN;
		function UnlinkMB:BOOLEAN;
		function ReIndexMB:BOOLEAN;

		function UpdHdrInfo(WriteIt:BOOLEAN):BOOLEAN;
		function LockMB(FetchHdrInfo:BOOLEAN):BOOLEAN;
		function UnlockMB(UpdateHdrInfo:BOOLEAN):BOOLEAN;

		function FindField(WhatField:LONGINT; var Position:LONGINT; MaxToScan:LONGINT):BOOLEAN;
		function AddField(WhatField:LONGINT; First:Boolean; DatLen:WORD; var Position:LONGINT; var Data):BOOLEAN;

		function FetchMsgIdx(WhatMsg:LONGINT):BOOLEAN;
		function FetchMsgHdr(WhatMsg:LONGINT; WithSubFields:BOOLEAN):BOOLEAN;
		function FetchNextMsgHdr(WithSubFields:BOOLEAN):BOOLEAN;
		function FetchPrevMsgHdr(WithSubFields:BOOLEAN):BOOLEAN;
		function FetchMsgTxt(FirstFetch:BOOLEAN):BOOLEAN;

		function ScanForMsgIdx(StartNum:LONGINT; ScanFwd:BOOLEAN; UserCompare:ScanMsgIdxFunc):BOOLEAN;
		function ScanForMsgHdr(StartNum:LONGINT; ScanFwd:BOOLEAN; UserCompare:ScanMsgHdrFunc):BOOLEAN;

		function StoreMsgHdr(WhatMsg:LONGINT):BOOLEAN;
		function StoreMsgIdx(WhatMsg:LONGINT):BOOLEAN;
		function StoreMsgTxt:BOOLEAN;
		function StoreMsgTxtBuf(var Buffer; BufLen:WORD; IsFirst:BOOLEAN):BOOLEAN;

		function FetchLastRead(UserID:LONGINT):BOOLEAN;
		function StoreLastRead(UpdateHdrInfo:BOOLEAN):BOOLEAN;
	END;{JAMAPI}

IMPLEMENTATION

USES
	Dos,
	JAMCRC32;

	{-Initializes object and its data members.}
	constructor JAMAPI.Init;
	BEGIN
		BaseName:='';
		WorkBuf:=NIL;
		WorkLen:=0;
		WorkPos:=0;
		IsOpen:=FALSE;
		HaveLock:=FALSE;
		Errno:=0;
		APImsg:=JAMAPIMSG_NOTHING;
		HdrHandle:=-1;
		TxtHandle:=-1;
		IdxHandle:=-1;
		LrdHandle:=-1;
		LastMsgNum:=0;
		LastLRDnum:=0;
	END;

	{-Deinitializes object and closes message base if it's open}
	destructor JAMAPI.Done;
	BEGIN
{$IFDEF VER55}
		if (not CloseMB) then
			;
{$ELSE}
		CloseMB;
{$ENDIF}
	END;

	{-DOS file access functions, defined as VIRTUAL to be overriden by an
		object that inherits this object if you feel that these are not
		sufficient. -------------------------------------------------------------}

	{-Create file, return handle or -1 on error}
	function JAMAPI.CreateFile(FileName:FILENAMETYPE):INTEGER;
	VAR
		az : ASCIIZ;
		r : registers;
	BEGIN
		{NUL terminate filename for DOS}
		move(FileName[1], az[1], length(FileName));
		az[Succ(length(FileName))]:=#0;

		r.AH:=$3c;                         {INT 21: Create file}
		r.CX:=0;                           {No special file attribute}
		r.DS:=seg(az);                     {Filename}
		r.DX:=ofs(az);
		msdos(r);                          {Call DOS}
		if (r.Flags and FCarry<>0) then    {Check carry flag (=error)}
			BEGIN
				Errno:=r.AX;                   {Save DOS error}
				CreateFile:=-1;                {Return error}				
			END
		ELSE
			CreateFile:=r.AX;                {No error, return handle}
	END;

	{-Open file, return handle or -1 on error, mode is ReadWrite/DenyNone/
		NoInherit}
	function JAMAPI.OpenFile(FileName:FILENAMETYPE):INTEGER;
	VAR
		az : ASCIIZ;
		r : registers;
	BEGIN
		{NUL terminate filename for DOS}
		move(FileName[1], az[1], length(FileName));
		az[Succ(length(FileName))]:=#0;

		r.AH:=$3d;                         {INT 21: Open file}
		r.AL:=194;                         {11000010: ReadWrite/DenyNone/NoInherit}
		r.DS:=seg(az);                     {Filename}
		r.DX:=ofs(az);
		msdos(r);                          {Call DOS}
		if (r.Flags and FCarry<>0) then    {Check carry flag (=error)}
			BEGIN
				Errno:=r.AX;                   {Save DOS error}
				OpenFile:=-1;                  {Return error}				
			END
		ELSE
			OpenFile:=r.AX;                  {No error, return handle}
	END;

	{-Close file pointed to by handle, return 0 or -1 on error}
	function JAMAPI.CloseFile(Handle:INTEGER):INTEGER;
	VAR
		r : registers;
	BEGIN
		r.AH:=$3e;                         {INT 21: Close file}
		r.BX:=Handle;                      {File handle}
		msdos(r);                          {Call DOS}
		if (r.Flags and FCarry<>0) then    {Check carry flag (=error)}
			BEGIN
				Errno:=r.AX;                   {Save DOS error}
				CloseFile:=-1;                 {Return error}				
			END
		ELSE
			CloseFile:=0;                    {No error}
	END;

	{-Unlink (delete) file, return handle or -1 on error}
	function JAMAPI.UnlinkFile(FileName:FILENAMETYPE):INTEGER;
	VAR
		az : ASCIIZ;
		r : registers;
	BEGIN
		{NUL terminate filename for DOS}
		move(FileName[1], az[1], length(FileName));
		az[Succ(length(FileName))]:=#0;

		r.AH:=$41;                         {INT 21: Unlink (delete) file}
		r.DS:=seg(az);                     {Filename}
		r.DX:=ofs(az);
		msdos(r);                          {Call DOS}
		if (r.Flags and FCarry<>0) then    {Check carry flag (=error)}
			BEGIN
				Errno:=r.AX;                   {Save DOS error}
				UnlinkFile:=-1;                {Return error}				
			END
		ELSE
			UnlinkFile:=0;                   {No error}
	END;

	{-Lock or unlock header file, return 0 or -1 on error}
	function JAMAPI.LockFile(DoLock:BOOLEAN):INTEGER;
	VAR
		r : registers;
	BEGIN
		r.AH:=$5c;                         {INT 21: Lock/Unlock file region}
		if (DoLock) then
			r.AL:=0                          {Lock file, AL=0}
		ELSE
			r.AL:=1;                         {Unlock file, AL=1}
		r.BX:=HdrHandle;                   {File handle (header file)}
		r.CX:=0;                           {Offset of region, high-order word}
		r.DX:=0;                           {Offset of region, low-order word}
		r.SI:=0;                           {Length of region, high-order word}
		r.DI:=1;                           {Length of region, low-order word}

		msdos(r);                          {Call DOS}
		if (r.Flags and FCarry<>0) then    {Check carry flag (=error)}
			BEGIN
				Errno:=r.AX;                   {Save DOS error}
				LockFile:=-1;                  {Return error}				
			END
		ELSE
			BEGIN
				LockFile:=0;                   {No error}
				HaveLock:=DoLock;              {Make sure we know status}
			END;
	END;

	{-Seek to specified position (relative to FROMWHERE) in file, returns new
		position or -1 on error}
	function JAMAPI.SeekFile(Handle:INTEGER; FromWhere:BYTE; FilePos:LONGINT):LONGINT;
	VAR
		r : registers;
	BEGIN
		r.AH:=$42;                         {INT 21: Move file pointer (LSEEK)}
		r.AL:=FromWhere;                   {From where to seek}
		r.BX:=Handle;                      {File handle}
		r.CX:=WORD(FilePos shr 16);        {Offset to seek to}
		r.DX:=WORD(FilePos);               {Offset to seek to}
		msdos(r);                          {Call DOS}
		if (r.Flags and FCarry<>0) then    {Check carry flag (=error)}
			BEGIN
				Errno:=r.AX;                   {Save DOS error}
				SeekFile:=$FFFFFFFF;           {Return error}				
			END
		ELSE
			SeekFile:=(LONGINT(r.DX) shl 16) or LONGINT(r.AX); {Return new position}
	END;

	{-Read data from file pointed to by Handle into Buffer. The maximum amount
		of data to read is contained in DatLen. The function returns the number
		of bytes read or $ffff on error.}
	function JAMAPI.ReadFile(Handle:INTEGER; VAR Buffer; DatLen:WORD):WORD;
	VAR
		r : registers;
	BEGIN
		r.AH:=$3f;                         {INT 21: Read from file or device}
		r.BX:=Handle;                      {File handle}
		r.DS:=seg(Buffer);                 {Buffer}
		r.DX:=ofs(Buffer);
		r.CX:=DatLen;                      {Amount to read (bytes)}
		msdos(r);                          {Call DOS}
		if (r.Flags and FCarry<>0) then    {Check carry flag (=error)}
			BEGIN
				Errno:=r.AX;                   {Save DOS error}
				ReadFile:=$ffff;               {Return error}				
			END
		ELSE
			ReadFile:=r.AX;                  {Return amount read}
	END;

	{-Write data from Buffer to file pointed to by Handle. The maximum amount
		of data to write is contained in DatLen. The function returns the number
		of bytes written or $ffff on error.}
	function JAMAPI.WriteFile(Handle:INTEGER; VAR Buffer; DatLen:WORD):WORD;
	VAR
		r : registers;
	BEGIN
		r.AH:=$40;                         {INT 21: Write to file or device}
		r.BX:=Handle;                      {File handle}
		r.DS:=seg(Buffer);                 {Buffer}
		r.DX:=ofs(Buffer);
		r.CX:=DatLen;                      {Amount to write (bytes)}
		msdos(r);                          {Call DOS}
		if (r.Flags and FCarry<>0) then    {Check carry flag (=error)}
			BEGIN
				Errno:=r.AX;                   {Save DOS error}
				WriteFile:=$ffff;              {Return error}				
			END
		ELSE
			WriteFile:=r.AX;                 {Return amount written}
	END;

	{-Function to convert uppercase to lowercase}

	procedure JAMAPI.LowCaseBuf(var Buf; DatLen:WORD);
	VAR
		JunkArray : ARRAY[1..MAXINT] of CHAR ABSOLUTE Buf;
		W : WORD;
	BEGIN
		for w:=1 to DatLen do
			if (JunkArray[w]>='A') and (JunkArray[w]<='Z') then
				inc(JunkArray[w], 32);
	END;

	{-JAM message base access functions. --------------------------------------}

	{-Opens message base. Returns TRUE on success or FALSE on error}
	function JAMAPI.OpenMB:BOOLEAN;
	VAR
		FileName : FILENAMETYPE;
{$IFDEF VER55}
		JunkRet : INTEGER;
{$ENDIF}
	BEGIN
		OpenMB:=FALSE;

		{Make sure it's not already open}
		if (IsOpen) then
			BEGIN
				APImsg:=JAMAPIMSG_ISOPEN;
				OpenMB:=TRUE;
				exit;
			END
		ELSE
			APImsg:=JAMAPIMSG_NOTHING;


		{.JHR file}
		FileName:=BaseName+EXT_HDRFILE;
		HdrHandle:=OpenFile(FileName);
		if (HdrHandle<0) then
			exit;

		{Read fixed size block from header file}
		if (ReadFile(HdrHandle, HdrInfo, sizeof(JAMHDRINFO))<>sizeof(JAMHDRINFO)) then
			BEGIN
{$IFDEF VER55}
				JunkRet:=CloseFile(HdrHandle);
{$ELSE}
				CloseFile(HdrHandle);
{$ENDIF}
				exit;
			END;
		if (HdrInfo.BaseMsgNum=0) then
			inc(HdrInfo.BaseMsgNum);

		{.JDT file}
		FileName:=BaseName+EXT_TXTFILE;
		TxtHandle:=OpenFile(FileName);
		if (TxtHandle<0) then
			BEGIN
{$IFDEF VER55}
				JunkRet:=CloseFile(HdrHandle);
{$ELSE}
				CloseFile(HdrHandle);
{$ENDIF}
				exit;
			END;

		{.JDX file}
		FileName:=BaseName+EXT_IDXFILE;
		IdxHandle:=OpenFile(FileName);
		if (IdxHandle<0) then
			BEGIN
{$IFDEF VER55}
				JunkRet:=CloseFile(HdrHandle);
				JunkRet:=CloseFile(TxtHandle);
{$ELSE}
				CloseFile(HdrHandle);
				CloseFile(TxtHandle);
{$ENDIF}
				exit;
			END;

		{.JLR file}
		FileName:=BaseName+EXT_LRDFILE;
		LrdHandle:=OpenFile(FileName);
		if (LrdHandle<0) then
			BEGIN
{$IFDEF VER55}
				JunkRet:=CloseFile(HdrHandle);
				JunkRet:=CloseFile(TxtHandle);
				JunkRet:=CloseFile(IdxHandle);
{$ELSE}
				CloseFile(HdrHandle);
				CloseFile(TxtHandle);
				CloseFile(IdxHandle);
{$ENDIF}
				exit;
			END;

		{Everything went OK, return with success}
		IsOpen:=TRUE;
		OpenMB:=TRUE;
	END;

	{-Creates new message base. Returns TRUE on success or FALSE on error}
	function JAMAPI.CreateMB:BOOLEAN;
	VAR
		FileName : FILENAMETYPE;
{$IFDEF VER55}
		JunkRet : INTEGER;
{$ENDIF}
	BEGIN
		CreateMB:=FALSE;

		{Make sure it's not already open}
		if (IsOpen) then
			BEGIN
				APImsg:=JAMAPIMSG_ISOPEN;
				CreateMB:=TRUE;
				exit;
			END
		ELSE
			APImsg:=JAMAPIMSG_NOTHING;


		{.JHR file}
		FileName:=BaseName+EXT_HDRFILE;
		HdrHandle:=CreateFile(FileName);
		if (HdrHandle<0) then
			exit;

		{Write fixed size block to header file}
		fillchar(HdrInfo, sizeof(JAMHDRINFO), 0);
		HdrInfo.PasswordCRC:=$FFFFFFFF;
		move(HEADERSIG, HdrInfo.Signature, sizeof(HdrInfo.Signature));
    HdrInfo.BaseMsgNum:=1;

		{-NOTE: The "DateCreated" field is still not filled in properly in the
						TP API}

		if (WriteFile(HdrHandle, HdrInfo, sizeof(JAMHDRINFO))<>sizeof(JAMHDRINFO)) then
			BEGIN
{$IFDEF VER55}
				JunkRet:=CloseFile(HdrHandle);
				JunkRet:=UnlinkFile(FileName);
{$ELSE}
				CloseFile(HdrHandle);
				UnlinkFile(FileName);
{$ENDIF}
				exit;
			END;

		{.JDT file}
		FileName:=BaseName+EXT_TXTFILE;
		TxtHandle:=CreateFile(FileName);
		if (TxtHandle<0) then
			BEGIN
{$IFDEF VER55}
				JunkRet:=CloseFile(HdrHandle);
{$ELSE}
				CloseFile(HdrHandle);
{$ENDIF}
				exit;
			END;

		{.JDX file}
		FileName:=BaseName+EXT_IDXFILE;
		IdxHandle:=CreateFile(FileName);
		if (IdxHandle<0) then
			BEGIN
{$IFDEF VER55}
				JunkRet:=CloseFile(HdrHandle);
				JunkRet:=CloseFile(TxtHandle);
{$ELSE}
				CloseFile(HdrHandle);
				CloseFile(TxtHandle);
{$ENDIF}
				exit;
			END;

		{.JLR file}
		FileName:=BaseName+EXT_LRDFILE;
		LrdHandle:=CreateFile(FileName);
		if (LrdHandle<0) then
			BEGIN
{$IFDEF VER55}
				JunkRet:=CloseFile(HdrHandle);
				JunkRet:=CloseFile(TxtHandle);
				JunkRet:=CloseFile(IdxHandle);
{$ELSE}
				CloseFile(HdrHandle);
				CloseFile(TxtHandle);
				CloseFile(IdxHandle);
{$ENDIF}
				exit;
			END;

		{Everything went OK, return with success}
		IsOpen:=TRUE;
		CreateMB:=TRUE;
	END;

	{-Close open message base. Returns TRUE on success or FALSE on error}
	function JAMAPI.CloseMB:BOOLEAN;
{$IFDEF VER55}
	VAR
		JunkRet : INTEGER;
{$ENDIF}
	BEGIN
		{Make sure it's not already open}
		if (IsOpen) then
			BEGIN
				APImsg:=JAMAPIMSG_NOTHING;
				if (HaveLock) then
{$IFDEF VER55}
					JunkRet:=LockFile(FALSE);
				JunkRet:=CloseFile(HdrHandle);
				JunkRet:=CloseFile(TxtHandle);
				JunkRet:=CloseFile(IdxHandle);
				JunkRet:=CloseFile(LrdHandle);
{$ELSE}
					LockFile(FALSE);
				CloseFile(HdrHandle);
				CloseFile(TxtHandle);
				CloseFile(IdxHandle);
				CloseFile(LrdHandle);
{$ENDIF}
				IsOpen:=FALSE;
			END
		ELSE
			APImsg:=JAMAPIMSG_ISNOTOPEN;
		CloseMB:=TRUE;
	END;

	{-Deletes existing (closed) message base. Returns TRUE on success or FALSE
		on error}
	function JAMAPI.UnlinkMB:BOOLEAN;
	VAR
		FileName : FILENAMETYPE;
		j1, j2, j3, j4 : INTEGER;
	BEGIN
		{Make sure it's not already open}
		if (IsOpen) then
			BEGIN
				APImsg:=JAMAPIMSG_ISOPEN;
				UnlinkMB:=FALSE;
				exit;
			END
		ELSE
			APImsg:=JAMAPIMSG_NOTHING;

		{.JDH file}
		FileName:=BaseName+EXT_HDRFILE;
		j1:=UnlinkFile(FileName);

		{.JDT file}
		FileName:=BaseName+EXT_TXTFILE;
		j2:=UnlinkFile(FileName);

		{.JDX file}
		FileName:=BaseName+EXT_IDXFILE;
		j3:=UnlinkFile(FileName);

		{.JLR file}
		FileName:=BaseName+EXT_LRDFILE;
		j4:=UnlinkFile(FileName);

		UnlinkMB:=BOOLEAN((j1=0) and (j2=0) and (j3=0) and (j4=0));
	END;

	{-Reindex an open message base. The message base must be locked by the
		application prior to calling this function. Returns TRUE on success or
		FALSE on error.}
	function JAMAPI.ReIndexMB:BOOLEAN;
	VAR
		FileName : FILENAMETYPE;
		TempIdxRec, IdxRec : JAMIDXREC;
		JunkBuf : ARRAY[1..110] of BYTE;
		SubPtr : JAMSUBFIELDPTR;
		NextOffset, CurIdxOffset, NextIdxOffset, IdxSize, JunkLong : LONGINT;
		Finished, Error, FoundField : BOOLEAN;
		SubDatLen, Junk : WORD;
	BEGIN
		ReIndexMB:=FALSE;

		{Make sure it's open}
		if (not IsOpen) then
			BEGIN
				APImsg:=JAMAPIMSG_ISNOTOPEN;
				exit;
			END;

		{Make sure we have lock}
		if (not HaveLock) then
			BEGIN
				APImsg:=JAMAPIMSG_ISNOTLOCKED;
				exit;
			END;

		{Close index file}
		if (IdxHandle>=0) then
			BEGIN
{$IFDEF VER55}
				IdxHandle:=CloseFile(IdxHandle);
{$ELSE}
				CloseFile(IdxHandle);
{$ENDIF}
				IdxHandle:=-1;
			END;

		{Create new index file}
		FileName:=BaseName+EXT_IDXFILE;
		IdxHandle:=CreateFile(FileName);
		if (IdxHandle<0) then
			BEGIN
				APImsg:=JAMAPIMSG_CANTMKFILE;
				exit;
			END;

		{Process header file}
		NextOffset:=LONGINT(sizeof(JAMHDRINFO));
		CurIdxOffset:=0;
		IdxSize:=0;
		Finished:=FALSE;
		Error:=FALSE;
		SubPtr:=@JunkBuf;

		WHILE ((not Finished) and (not Error)) DO
			BEGIN
				{Seek to correct position in header file}
				if (SeekFile(HdrHandle, JAMSEEK_SET, NextOffset)<>NextOffset) then
					BEGIN
						APImsg:=JAMAPIMSG_SEEKERROR;
						exit;
					END
				ELSE
					BEGIN
						{Read header}
						Junk:=ReadFile(HdrHandle, Hdr, sizeof(JAMHDR));
						if (Junk<>sizeof(JAMHDR)) then
							BEGIN
								if (Junk=0) then
									{Nothing left to read}
									Finished:=TRUE
								ELSE
									{Read error}
									BEGIN
										APImsg:=JAMAPIMSG_CANTRDFILE;
										Error:=TRUE;
									END;
							END
						ELSE
							BEGIN
								{Set values}
								IdxRec.HdrOffset:=NextOffset;

								{-Search subfields if we have any and this header hasn't been
									deleted}
								if ((Hdr.SubfieldLen>0) and (Hdr.Attribute and MSG_DELETED=0)) then
									BEGIN
										FoundField:=FALSE;
										JunkLong:=0;

										REPEAT
											Junk:=ReadFile(HdrHandle, JunkBuf, 4);
											if (Junk<>4) then
												BEGIN
													if (Junk=0) then
														Finished:=TRUE
													ELSE
														BEGIN
															APImsg:=JAMAPIMSG_CANTRDFILE;
															Error:=TRUE;
														END;
												END
											ELSE
												BEGIN
													inc(JunkLong, 4);
													SubDatLen:=WORD(SubPtr^.DatLen);

													{Is this what we're looking for?}
													if (SubPtr^.LoID=JAMSFLD_RECVRNAME) then
														BEGIN
															{Yes, read username}
															Junk:=ReadFile(HdrHandle, JunkBuf, SubDatLen);
															if (Junk=SubDatLen) then
																FoundField:=TRUE
															ELSE
																BEGIN
																	APImsg:=JAMAPIMSG_CANTRDFILE;
																	Error:=TRUE;
																END;
														END
													ELSE
														{No, skip to next header}
														BEGIN
															inc(JunkLong, SubPtr^.DatLen);
															if (SeekFile(HdrHandle, JAMSEEK_CUR, SubPtr^.DatLen)<0) then
																BEGIN
																	APImsg:=JAMAPIMSG_SEEKERROR;
																	Error:=TRUE;
																END;
														END;
												END;
										UNTIL (FoundField) or (JunkLong>=Hdr.SubfieldLen) or (Error) or (Finished);

										if (FoundField) then
											BEGIN
												LowCaseBuf(JunkBuf, SubDatLen);
												IdxRec.UserCRC:=crc32(JunkBuf, SubDatLen, $FFFFFFFF);
											END
										ELSE
											IdxRec.UserCRC:=$FFFFFFFF;
									END
								ELSE
									{No subfields}
									IdxRec.UserCRC:=$FFFFFFFF;

								{Skip message if invalid message number}
								if (Hdr.MsgNum>=HdrInfo.BaseMsgNum) then
									BEGIN
										if (not Error) then
											BEGIN
                        NextIdxOffset:=(Hdr.MsgNum-HdrInfo.BaseMsgNum)*LONGINT(sizeof(JAMIDXREC));
												if (CurIdxOffset<>NextIdxOffset) then
													BEGIN
														if (NextIdxOffset>IdxSize) then
															BEGIN
																if (SeekFile(IdxHandle, JAMSEEK_SET, IdxSize)<>IdxSize) then
																	BEGIN
																		APImsg:=JAMAPIMSG_SEEKERROR;
																		Error:=TRUE;
																	END;

																{Extend index and fill with empty index records}
																TempIdxRec.UserCRC:=$ffffffff;
																TempIdxRec.HdrOffset:=$ffffffff;
																WHILE (not Error) and (IdxSize<>NextIdxOffset) DO
																	BEGIN
																		if (WriteFile(IdxHandle, TempIdxRec, sizeof(JAMIDXREC))<>sizeof(JAMIDXREC)) then
																			BEGIN
                                        APImsg:=JAMAPIMSG_CANTWRFILE;
                                        Error:=TRUE;
																			END
																		ELSE
																			inc(IdxSize, LONGINT(sizeof(JAMIDXREC)));
																	END;
															END
														ELSE
															if (SeekFile(IdxHandle, JAMSEEK_SET, NextIdxOffset)<>NextIdxOffset) then
																BEGIN
																	APImsg:=JAMAPIMSG_SEEKERROR;
																	Error:=TRUE;
																END;
													END
											END;

										if (not Error) then
											BEGIN
												{Write index record}
												if (WriteFile(IdxHandle, IdxRec, sizeof(JAMIDXREC))<>sizeof(JAMIDXREC)) then
													BEGIN
														{Write error}
														APImsg:=JAMAPIMSG_CANTWRFILE;
														Error:=TRUE;
													END
												ELSE
													BEGIN
                            CurIdxOffset:=NextIdxOffset+LONGINT(sizeof(JAMIDXREC));
                            if (CurIdxOffset>IdxSize) then
                                IdxSize:=CurIdxOffset;
														inc(NextOffset, LONGINT(sizeof(JAMHDR))+LONGINT(Hdr.SubfieldLen));
													END;
											END;
									END;
							END;{ReadOK}
					END;{SeekOK}
			END;{while}
		ReIndexMB:=(not Error);
	END;

	{-Fetch or update message header info block. Returns TRUE on success and
		FALSE on failure.}
	function JAMAPI.UpdHdrInfo(WriteIt:BOOLEAN):BOOLEAN;
	BEGIN
		UpdHdrInfo:=FALSE;

		{Make sure it's open}
		if (not IsOpen) then
			BEGIN
				APImsg:=JAMAPIMSG_ISNOTOPEN;
				exit;
			END;

		{Make sure we have lock if we need it}
		if (WriteIt) and (not HaveLock) then
			BEGIN
				APImsg:=JAMAPIMSG_ISNOTLOCKED;
				exit;
			END;

		{Seek to beginning of file}
		if (SeekFile(HdrHandle, JAMSEEK_SET, 0)<>0) then
			BEGIN
				APImsg:=JAMAPIMSG_SEEKERROR;
				exit;
			END;

		if (WriteIt) then
			BEGIN
				{Update}
				inc(HdrInfo.ModCounter);

				if (HdrInfo.BaseMsgNum=0) then
					inc(HdrInfo.BaseMsgNum);

				if (WriteFile(HdrHandle, HdrInfo, sizeof(JAMHDRINFO))<>sizeof(JAMHDRINFO)) then
					BEGIN
						dec(HdrInfo.ModCounter);
						APImsg:=JAMAPIMSG_CANTWRFILE;
						exit;
					END;
			END
		ELSE
			{Read}
			BEGIN
				if (ReadFile(HdrHandle, HdrInfo, sizeof(JAMHDRINFO))<>sizeof(JAMHDRINFO)) then
					BEGIN
						APImsg:=JAMAPIMSG_CANTRDFILE;
						exit;
					END;
				if (HdrInfo.BaseMsgNum=0) then
					inc(HdrInfo.BaseMsgNum);
			END;

		APImsg:=JAMAPIMSG_NOTHING;
		UpdHdrInfo:=TRUE;
	END;

	{-Locks message base and if successful, optionally reads the header info
		block at the beginning of the header file. Returns TRUE on success or
		FALSE on failure.}
	function JAMAPI.LockMB(FetchHdrInfo:BOOLEAN):BOOLEAN;
	BEGIN
		LockMB:=FALSE;

		{Make sure it's open}
		if (not IsOpen) then
			BEGIN
				APImsg:=JAMAPIMSG_ISNOTOPEN;
				exit;
			END;

		{Attempt to lock it if we don't already have a lock}
		if (not HaveLock) and (LockFile(TRUE)<>0) then
			BEGIN
				APImsg:=JAMAPIMSG_CANTLKFILE;
				exit;
			END;

		{Read header info block if told to}
		if (FetchHdrInfo) then
			BEGIN
				if (not UpdHdrInfo(FALSE)) then
					exit;
			END;

		APImsg:=JAMAPIMSG_NOTHING;
		LockMB:=TRUE;
	END;


	{-Unlocks message base and if successful, optionally writes the header
		info block to the beginning of the header file. Returns TRUE upon
		success and FALSE upon failure.}
	function JAMAPI.UnlockMB(UpdateHdrInfo:BOOLEAN):BOOLEAN;
	BEGIN
		UnlockMB:=FALSE;

		{Make sure it's open}
		if (not IsOpen) then
			BEGIN
				APImsg:=JAMAPIMSG_ISNOTOPEN;
				exit;
			END;

		{Make sure we have a lock}
		if (not HaveLock) then
			BEGIN
				APImsg:=JAMAPIMSG_ISNOTLOCKED;
				exit;
			END;

		{Update header info if told to before unlocking}
		if (UpdateHdrInfo) then
			BEGIN
				if (not UpdHdrInfo(TRUE)) then
					exit;
			END;

		{Unlock the file}
{$IFDEF VER55}
		APImsg:=LockFile(FALSE);
{$ELSE}
		LockFile(FALSE);
{$ENDIF}

		APImsg:=JAMAPIMSG_NOTHING;
		UnlockMB:=TRUE;
	END;

	{-Find specified field in WorkBuf. Returns TRUE if the field is found and
		FALSE if the field is not found. The POSITION parameter is updated to
		point to the beginning of the found field.}
	function JAMAPI.FindField(WhatField:LONGINT; var Position:LONGINT; MaxToScan:LONGINT):BOOLEAN;
	BEGIN
		WHILE (Position<MaxToScan) DO
			BEGIN
				SubFieldPtr:=@WorkBuf^[Position];
				if (SubFieldPtr^.LoID=WORD(WhatField)) then
					BEGIN
						FindField:=TRUE;
						exit;
					END;
				inc(Position, SubFieldPtr^.DatLen + sizeof(JAMBINSUBFIELD));
			END;{while}
		FindField:=FALSE;
	END;

	{-Add specified field to WorkBuf. It requires that POSITION has been reset
		to one (1) prior to the first call. The POSITION parameter is updated to
		point to the first position after the newly added field. The FIRST
		parameter determines if the DatLen/ID fields should be copied. This
		allows multiple calls to this function to add more than 64kb to a
		subfield. Returns TRUE on success or FALSE if the requested field does
		not fit into WorkBuf}
	function JAMAPI.AddField(WhatField:LONGINT; First:Boolean; DatLen:WORD; var Position:LONGINT; var Data):BOOLEAN;
	VAR
		DummyField : JAMBINSUBFIELDPTR;
	BEGIN
		AddField:=TRUE;
		if (First) then
			BEGIN
				if ((LONGINT(sizeof(JAMBINSUBFIELD))+Pred(Position)+DatLen)>WorkLen) then
					BEGIN
						AddField:=FALSE;
						exit;
					END;
				DummyField:=@WorkBuf^[Position];
				DummyField^.LoID:=WORD(WhatField);
				DummyField^.HiID:=0;
				DummyField^.DatLen:=DatLen;
				inc(Position, sizeof(JAMBINSUBFIELD));
			END
		ELSE
			if ((DatLen+Pred(Position))>WorkLen) then
				BEGIN
					AddField:=FALSE;
					exit;
				END;
		move(Data, WorkBuf^[Position], DatLen);
		inc(Position, LONGINT(DatLen));
	END;

	{-Scan message index records starting at the specified number (STARTNUM).
		For each record found, the function calls USERCOMPARE. The Forward
		parameter determines the direction of the scan. The Idx record will
		contain the newly read message index record and. Returns TRUE if user
		function returned ScanMsgIdxStop and FALSE otherwise.}
	function JAMAPI.ScanForMsgIdx(StartNum:LONGINT; ScanFwd:BOOLEAN; UserCompare:ScanMsgIdxFunc):BOOLEAN;
	VAR
		WhatOffset : LONGINT;
		UserResult : INTEGER;
		ReadCount : WORD;
	BEGIN
		ScanForMsgIdx:=FALSE;

		{Make sure it's open}
		if (not IsOpen) then
			BEGIN
				APImsg:=JAMAPIMSG_ISNOTOPEN;
				exit;
			END;

    {Make sure the message number is valid}
		if (StartNum<HdrInfo.BaseMsgNum) then
			BEGIN
				APImsg:=JAMAPIMSG_INVMSGNUM;
				exit;
			END;

		{Position of first index record}
		WhatOffset:=LONGINT((StartNum-HdrInfo.BaseMsgNum) * LONGINT(sizeof(JAMIDXREC)));

		{Seek to correct position if we're going forward}
		if (ScanFwd) then
			BEGIN
				if (SeekFile(IdxHandle, JAMSEEK_SET, WhatOffset)<>WhatOffset) then
					BEGIN
						APImsg:=JAMAPIMSG_SEEKERROR;
						exit;
					END;
			END;

		{Scan index records}
		WHILE TRUE DO
			BEGIN
				{Seek to correct position if we're going backwards}
				if (not ScanFwd) then
					BEGIN
						if (SeekFile(IdxHandle, JAMSEEK_SET, WhatOffset)<>WhatOffset) then
							BEGIN
								APImsg:=JAMAPIMSG_SEEKERROR;
								exit;
							END;
					END;

				ReadCount:=ReadFile(IdxHandle, Idx, sizeof(JAMIDXREC));
				if (ReadCount=sizeof(JAMIDXREC)) then
					BEGIN
						LastMsgNum:=StartNum;
						
						{Call user function and process result}
						UserResult:=UserCompare(@Self);
						if (UserResult=ScanMsgIdxStop) then
							BEGIN
								ScanForMsgIdx:=TRUE;
								exit;
							END
						ELSE
							BEGIN
								if (ScanFwd) then
									inc(StartNum)
								ELSE
									BEGIN
										if (StartNum=HdrInfo.BaseMsgNum) then
											BEGIN
												APImsg:=JAMAPIMSG_NOMOREMSGS;
												exit;
											END
										ELSE
											BEGIN
												dec(StartNum);
												dec(WhatOffset, LONGINT(sizeof(JAMIDXREC)));
											END;
									END;
							END;
					END
				ELSE
					{Check for end of file, otherwise error}
					BEGIN
						if (ReadCount=0) and (ScanFwd) then
							APImsg:=JAMAPIMSG_NOMOREMSGS
						ELSE
							APImsg:=JAMAPIMSG_CANTRDFILE;
						exit;
					END;
			END;{while}
	END;

	{-Fetch specified message number. If WITHSUBFIELDS=TRUE, the function will
		attempt to read as much of the message's subfield data into the internal
		work buffer as possible. Returns TRUE on success and FALSE on failure.}
	function JAMAPI.FetchMsgHdr(WhatMsg:LONGINT; WithSubFields:BOOLEAN):BOOLEAN;
	VAR
		ReadCount : WORD;
	BEGIN
		FetchMsgHdr:=FALSE;

		{Fetch index record, checks for IsOpen}
		if (not FetchMsgIdx(WhatMsg)) then
			exit;

		{Fetch header}
		if (SeekFile(HdrHandle, JAMSEEK_SET, Idx.HdrOffset)<>Idx.HdrOffset) then
			BEGIN
				APImsg:=JAMAPIMSG_SEEKERROR;
				exit;
			END;
		if (ReadFile(HdrHandle, Hdr, sizeof(JAMHDR))<>sizeof(JAMHDR)) then
			BEGIN
				APImsg:=JAMAPIMSG_CANTRDFILE;
				exit;
			END;

		{Check header}
		if (Hdr.Signature<>HEADERSIG) then
			BEGIN
				APImsg:=JAMAPIMSG_BADHEADERSIG;
				exit;
			END;
		if (Hdr.Revision<>CurrentRevLev) then
			BEGIN
				APImsg:=JAMAPIMSG_BADHEADERREV;
				exit;
			END;

		{Fetch subfields if told to}
		if (WithSubFields) then
			BEGIN
				if (Hdr.SubfieldLen>WorkLen) then
					ReadCount:=WORD(WorkLen)
				ELSE
					ReadCount:=WORD(Hdr.SubfieldLen);
			END
		ELSE
			ReadCount:=0;

		if (ReadCount<>0) then
			BEGIN
				if (ReadFile(HdrHandle, WorkBuf^, ReadCount)<>ReadCount) then
					BEGIN
						APImsg:=JAMAPIMSG_CANTRDFILE;
						exit;
					END;
			END;

		{Got it OK}
		APImsg:=JAMAPIMSG_NOTHING;
		FetchMsgHdr:=TRUE;
	END;

	{-Fetch index record with specified message number. Returns TRUE on success
		and FALSE on failure.}
	function JAMAPI.FetchMsgIdx(WhatMsg:LONGINT):BOOLEAN;
	VAR
		WhatOffset : LONGINT;
	BEGIN
		FetchMsgIdx:=FALSE;

		{Make sure it's open}
		if (not IsOpen) then
			BEGIN
				APImsg:=JAMAPIMSG_ISNOTOPEN;
				exit;
			END;

    {Make sure the message number is valid}
    if (WhatMsg<HdrInfo.BaseMsgNum) then
			BEGIN
				APImsg:=JAMAPIMSG_INVMSGNUM;
				exit;
			END;

		{Fetch index record}
		WhatOffset:=LONGINT((WhatMsg-HdrInfo.BaseMsgNum) * LONGINT(sizeof(JAMIDXREC)));
		if (SeekFile(IdxHandle, JAMSEEK_SET, WhatOffset)<>WhatOffset) then
			BEGIN
				APImsg:=JAMAPIMSG_SEEKERROR;
				exit;
			END;
		if (ReadFile(IdxHandle, Idx, sizeof(JAMIDXREC))<>sizeof(JAMIDXREC)) then
			BEGIN
				APImsg:=JAMAPIMSG_CANTRDFILE;
				exit;
			END;

		{Update structure}
		LastMsgNum:=WhatMsg;
		APImsg:=JAMAPIMSG_NOTHING;
		FetchMsgIdx:=TRUE;
	END;

	{-Fetch header for message number following LASTMSGNUM. Returns TRUE on
		success and FALSE on failure.}
	function JAMAPI.FetchNextMsgHdr(WithSubFields:BOOLEAN):BOOLEAN;
	BEGIN
		FetchNextMsgHdr:=FetchMsgHdr(Succ(LastMsgNum), WithSubFields);
	END;

	{-Fetch header for message number preceding LASTMSGNUM. Returns TRUE on
		success and FALSE on failure.}
	function JAMAPI.FetchPrevMsgHdr(WithSubFields:BOOLEAN):BOOLEAN;
	BEGIN
		if (LastMsgNum>1) then
			FetchPrevMsgHdr:=FetchMsgHdr(Pred(LastMsgNum), WithSubFields)
		ELSE
			BEGIN
				APImsg:=JAMAPIMSG_FIRSTMSG;
				FetchPrevMsgHdr:=FALSE;
			END;
	END;

	{-Fetch text for specified message number. FIRSTFETCH determines if the
		function seeks to the actual text position or simply keeps reading.
		Returns TRUE on success and FALSE on failure.}
	function JAMAPI.FetchMsgTxt(FirstFetch:BOOLEAN):BOOLEAN;
	VAR
		RemainToRead : LONGINT;
		ReadCount : WORD;
	BEGIN
		FetchMsgTxt:=FALSE;

		{Make sure it's open}
		if (not IsOpen) then
			BEGIN
				APImsg:=JAMAPIMSG_ISNOTOPEN;
				exit;
			END;

		{Seek to appropriate text position if this is first fetch}
		if (FirstFetch) then
			BEGIN
				if (SeekFile(TxtHandle, JAMSEEK_SET, Hdr.TxtOffset)<>Hdr.TxtOffset) then
					BEGIN
						APImsg:=JAMAPIMSG_SEEKERROR;
						exit;
					END
				ELSE
					{Haven't read anything yet}
					WorkPos:=0;
			END;

		{Make sure we don't read more than we have}
		if (WorkPos>=Hdr.TxtLen) then
			BEGIN
				APImsg:=JAMAPIMSG_NOMORETEXT;
				FetchMsgTxt:=TRUE;
				exit;
			END;

		{Figure out how much to get}
		RemainToRead:=(Hdr.TxtLen-WorkPos);
		if (RemainToRead>WorkLen) then
			BEGIN
				ReadCount:=WORD(WorkLen);
				if (WorkLen>$0000fffe) then
					ReadCount:=$fffe;
			END
		ELSE
			BEGIN
				ReadCount:=WORD(RemainToRead);
				if (RemainToRead>$0000fffe) then
					ReadCount:=$fffe;
			END;

		{Get it from disk}
		if (ReadFile(TxtHandle, WorkBuf^, ReadCount)<>ReadCount) then
			BEGIN
				APImsg:=JAMAPIMSG_CANTRDFILE;
				exit;
			END;

		{Got it OK}
		inc(WorkPos, LONGINT(ReadCount));
		APImsg:=JAMAPIMSG_NOTHING;
		FetchMsgTxt:=TRUE;
	END;

	{-Scan message headers starting at the specified number (STARTNUM). For
		each header found, the function calls USERCOMPARE. The Forward parameter
		determines the direction of the scan. The Hdr record will contain the
		newly read message header and if the header had any subfields, WorkBuf
		will contain as much subfield data as will fit. If all of the subfield
		data didn't fit and the USERCOMPARE function returns ScanMsgHdrDiscard,
		the function will read the remaining subfield data and call the user
		function again. Returns TRUE if user function returned ScanMsgHdrStop and
		FALSE otherwise.}
	function JAMAPI.ScanForMsgHdr(StartNum:LONGINT; ScanFwd:BOOLEAN; UserCompare:ScanMsgHdrFunc):BOOLEAN;
	VAR
		UserResult : INTEGER;
		MaxBlockToRead, JunkW : WORD;
		ReadBytes : LONGINT;
	BEGIN
		ScanForMsgHdr:=FALSE;

    {Make sure the message number is valid}
		if (StartNum<HdrInfo.BaseMsgNum) then
			BEGIN
				APImsg:=JAMAPIMSG_INVMSGNUM;
				exit;
			END;

		{Scan headers}
		WHILE TRUE DO
			BEGIN
				{Seek to correct position for current message}
				if (not FetchMsgIdx(StartNum)) then
						exit;
				if (SeekFile(HdrHandle, JAMSEEK_SET, Idx.HdrOffset)<>Idx.HdrOffset) then
					BEGIN
						APImsg:=JAMAPIMSG_SEEKERROR;
						exit;
					END;

				JunkW:=ReadFile(HdrHandle, Hdr, sizeof(JAMHDR));
				if (JunkW=sizeof(JAMHDR)) then
					BEGIN
						LastMsgNum:=StartNum;
						
						{If all subfield data will fit, read it and call user function}
						if (Hdr.SubfieldLen<=WorkLen) then
							BEGIN
								if (Hdr.SubfieldLen>0) then
									BEGIN
										if (ReadFile(HdrHandle, WorkBuf^, WORD(Hdr.SubfieldLen))<>WORD(Hdr.SubFieldLen)) then
											BEGIN
												APImsg:=JAMAPIMSG_CANTRDFILE;
												exit;
											END;
									END;

								{Call user function and process result}
								UserResult:=UserCompare(@Self);
								if (UserResult=ScanMsgHdrStop) then
									BEGIN
										ScanForMsgHdr:=TRUE;
										exit;
									END
								ELSE
									BEGIN
										if (ScanFwd) then
											inc(StartNum)
										ELSE
											BEGIN
												if (StartNum=HdrInfo.BaseMsgNum) then
													BEGIN
														APImsg:=JAMAPIMSG_NOMOREMSGS;
														exit;
													END
												ELSE
													dec(StartNum);
											END;
									END;
							END
						ELSE
							{All subfield data won't fit, do segmented read/call}
							BEGIN
								ReadBytes:=0;
								if (WorkLen>$0000fffe) then
									MaxBlockToRead:=$fffe
								ELSE
									MaxBlockToRead:=WORD(WorkLen);

								REPEAT
									if (ReadFile(HdrHandle, WorkBuf^, MaxBlockToRead)<>MaxBlockToRead) then
										BEGIN
											APImsg:=JAMAPIMSG_CANTRDFILE;
											exit;
										END;

									if (MaxBlockToRead>0) then
										UserResult:=UserCompare(@Self);

									inc(ReadBytes, LONGINT(MaxBlockToRead));
									if (Hdr.SubfieldLen-ReadBytes<LONGINT(MaxBlockToRead)) then
										MaxBlockToRead:=WORD(Hdr.SubfieldLen-ReadBytes);

								UNTIL (ReadBytes=Hdr.SubfieldLen) or
												(MaxBlockToRead=0) or
													(UserResult<>ScanMsgHdrDiscard);

								{We've read all subfield data or got told to do something else}

								if (UserResult=ScanMsgHdrStop) then
									BEGIN
										ScanForMsgHdr:=TRUE;
										exit;
									END
								ELSE
									BEGIN
										if (ScanFwd) then
											BEGIN
												{If we didn't read all data, seek to next header}
												if (MaxBlockToRead<>0) then
													BEGIN
														ReadBytes:=(Hdr.SubfieldLen-ReadBytes);
														if (SeekFile(HdrHandle, JAMSEEK_CUR, ReadBytes)<0) then
															BEGIN
																APImsg:=JAMAPIMSG_SEEKERROR;
																exit;
															END;
													END;
												inc(StartNum);
											END
										ELSE
											BEGIN
												if (StartNum=HdrInfo.BaseMsgNum) then
													BEGIN
														APImsg:=JAMAPIMSG_NOMOREMSGS;
														exit;
													END
												ELSE
													dec(StartNum);
											END;
									END;
							END;
					END
				ELSE
					{Check for end of file, otherwise error}
					BEGIN
						if (JunkW=0) and (ScanFwd) then
							APImsg:=JAMAPIMSG_NOMOREMSGS
						ELSE
							APImsg:=JAMAPIMSG_CANTRDFILE;
						exit;
					END;
			END;{while}
	END;

	{-Store message header with specified number. The HdrHandle's file offset
		will point to the end of the fixed-length header record when the function
		returns (if successful) so the application can write any subfields
		directly to the file. Returns TRUE on success and FALSE on failure.}
	function JAMAPI.StoreMsgHdr(WhatMsg:LONGINT):BOOLEAN;
	BEGIN
		StoreMsgHdr:=FALSE;

		{Make sure it's open}
		if (not IsOpen) then
			BEGIN
				APImsg:=JAMAPIMSG_ISNOTOPEN;
				exit;
			END;

		{Make sure it's locked}
		if (not HaveLock) then
			BEGIN
				APImsg:=JAMAPIMSG_ISNOTLOCKED;
				exit;
			END;

		{Fetch index record}
		if (not FetchMsgIdx(WhatMsg)) then
			exit;

		{Update structure, even if below fails}
		Hdr.MsgNum:=WhatMsg;

		{Make sure header signature and revision is OK}
		move(HEADERSIG, Hdr.Signature, sizeof(Hdr.Signature));
		Hdr.Revision:=CurrentRevLev;

		{Write header}
		if (SeekFile(HdrHandle, JAMSEEK_SET, Idx.HdrOffset)<>Idx.HdrOffset) then
			BEGIN
				APImsg:=JAMAPIMSG_SEEKERROR;
				exit;
			END;
		if (WriteFile(HdrHandle, Hdr, sizeof(JAMHDR))<>sizeof(JAMHDR)) then
			BEGIN
				APImsg:=JAMAPIMSG_CANTWRFILE;
				exit;
			END;

		{Wrote it OK}
		APImsg:=JAMAPIMSG_NOTHING;
		StoreMsgHdr:=TRUE;
	END;

	{-Store message index record with specified number. The IdxHandle's file
		offset will point to the end of the fixed-length index record when the
		function returns (if successful). Returns TRUE on success and FALSE on
		failure.}
	function JAMAPI.StoreMsgIdx(WhatMsg:LONGINT):BOOLEAN;
	VAR
		WhatOffset : LONGINT;
	BEGIN
		StoreMsgIdx:=FALSE;

		{Make sure it's open}
		if (not IsOpen) then
			BEGIN
				APImsg:=JAMAPIMSG_ISNOTOPEN;
				exit;
			END;

		{Make sure it's locked}
		if (not HaveLock) then
			BEGIN
				APImsg:=JAMAPIMSG_ISNOTLOCKED;
				exit;
			END;

		{Make sure the message number is valid}
    if (WhatMsg<HdrInfo.BaseMsgNum) then
			BEGIN
				APImsg:=JAMAPIMSG_INVMSGNUM;
				exit;
			END;

		{Write index record}
		WhatOffset:=LONGINT((WhatMsg-HdrInfo.BaseMsgNum) * LONGINT(sizeof(JAMIDXREC)));
		if (SeekFile(IdxHandle, JAMSEEK_SET, WhatOffset)<>WhatOffset) then
			BEGIN
				APImsg:=JAMAPIMSG_SEEKERROR;
				exit;
			END;
		if (WriteFile(IdxHandle, Idx, sizeof(JAMIDXREC))<>sizeof(JAMIDXREC)) then
			BEGIN
				APImsg:=JAMAPIMSG_CANTWRFILE;
				exit;
			END;

		{Wrote it OK}
		APImsg:=JAMAPIMSG_NOTHING;
		StoreMsgIdx:=TRUE;
	END;

	{-Store message text at current header's text position. The TxtHandle's
		file offset will point to the end of the written text block when the
		function returns (if successful). This assumes that the entire
		message text is stored in the internal buffer. See STOREMSGTXTBUF()
		for a function that takes an external parameter and length specifier.
		Returns TRUE on success and FALSE on failure.}
	function JAMAPI.StoreMsgTxt:BOOLEAN;
	BEGIN
		StoreMsgTxt:=FALSE;

		{Make sure it's open}
		if (not IsOpen) then
			BEGIN
				APImsg:=JAMAPIMSG_ISNOTOPEN;
				exit;
			END;

		{Make sure it's locked}
		if (not HaveLock) then
			BEGIN
				APImsg:=JAMAPIMSG_ISNOTLOCKED;
				exit;
			END;

		{Write text}
		if (SeekFile(TxtHandle, JAMSEEK_SET, Hdr.TxtOffset)<>Hdr.TxtOffset) then
			BEGIN
				APImsg:=JAMAPIMSG_SEEKERROR;
				exit;
			END;
		if (WriteFile(TxtHandle, WorkBuf^, WORD(Hdr.TxtLen))<>WORD(Hdr.TxtLen)) then
			BEGIN
				APImsg:=JAMAPIMSG_CANTWRFILE;
				exit;
			END;

		{Wrote it OK}
		APImsg:=JAMAPIMSG_NOTHING;
		StoreMsgTxt:=TRUE;
	END;

	{-Store message text at current header's text position. The TxtHandle's
		file offset will point to the end of the written text block when the
		function returns (if successful). This assumes that the entire
		message text is stored in the internal buffer. The ISFIRST parameter
		determines if the function should seek to the position specified in
		the header before writing. This allows multiple calls to write large
		message texts. Returns TRUE on success and FALSE on failure.}
	function JAMAPI.StoreMsgTxtBuf(var Buffer; BufLen:WORD; IsFirst:BOOLEAN):BOOLEAN;
	BEGIN
		StoreMsgTxtBuf:=FALSE;

		{Make sure it's open}
		if (not IsOpen) then
			BEGIN
				APImsg:=JAMAPIMSG_ISNOTOPEN;
				exit;
			END;

		{Make sure it's locked}
		if (not HaveLock) then
			BEGIN
				APImsg:=JAMAPIMSG_ISNOTLOCKED;
				exit;
			END;

		{Seek if told to}
		if (IsFirst) then
			BEGIN
				if (SeekFile(TxtHandle, JAMSEEK_SET, Hdr.TxtOffset)<>Hdr.TxtOffset) then
					BEGIN
						APImsg:=JAMAPIMSG_SEEKERROR;
						exit;
					END;
			END;

		{Write text}
		if (WriteFile(TxtHandle, Buffer, BufLen)<>BufLen) then
			BEGIN
				APImsg:=JAMAPIMSG_CANTWRFILE;
				exit;
			END;

		{Wrote it OK}
		APImsg:=JAMAPIMSG_NOTHING;
		StoreMsgTxtBuf:=TRUE;
	END;

	{-Fetch LastRead for passed UserID. Returns TRUE on success and FALSE on
		failure.}
	function JAMAPI.FetchLastRead(UserID:LONGINT):BOOLEAN;
	VAR
		ReadCount : WORD;
		LastReadRec : LONGINT;
	BEGIN
		FetchLastRead:=FALSE;

		{Make sure it's open}
		if (not IsOpen) then
			BEGIN
				APImsg:=JAMAPIMSG_ISNOTOPEN;
				exit;
			END;

		{Seek to beginning of file}
		if (SeekFile(LrdHandle, JAMSEEK_SET, 0)<>0) then
			BEGIN
				APImsg:=JAMAPIMSG_SEEKERROR;
				exit;
			END;

		{Read file from top to bottom}
		LastReadRec:=0;
		WHILE (TRUE) DO
			BEGIN
				ReadCount:=ReadFile(LrdHandle, LastRead, sizeof(JAMLREAD));
				if (ReadCount<>sizeof(JAMLREAD)) then
					BEGIN
						if (ReadCount=0) then
							{End of file}
							APImsg:=JAMAPIMSG_CANTFINDUSER
						ELSE
							{Read error}
							APImsg:=JAMAPIMSG_CANTRDFILE;
						exit;
					END;

				{See if it matches what we want}
				if (LastRead.UserID=UserID) then
					BEGIN
						LastLRDnum:=LastReadRec;
						APImsg:=JAMAPIMSG_NOTHING;
						FetchLastRead:=TRUE;
						exit;
					END;

				{Next record number}
				inc(LastReadRec);
			END;{while}
	END;

	{-Store LastRead record and if successful, optionall updates the header
		info block (and its ModCounter) at the beginning of the header file.
		Returns TRUE upon success and FALSE upon failure.}
	function JAMAPI.StoreLastRead(UpdateHdrInfo:BOOLEAN):BOOLEAN;
	VAR
		UserOffset : LONGINT;
	BEGIN
		StoreLastRead:=FALSE;

		{Make sure it's open}
		if (not IsOpen) then
			BEGIN
				APImsg:=JAMAPIMSG_ISNOTOPEN;
				exit;
			END;

		{Make sure it's locked}
		if (not HaveLock) then
			BEGIN
				APImsg:=JAMAPIMSG_ISNOTLOCKED;
				exit;
			END;

		{Seek to the appropriate position}
		UserOffset:=LONGINT(LastLRDnum * LONGINT(sizeof(JAMLREAD)));
		if (SeekFile(LrdHandle, JAMSEEK_SET, UserOffset)<>UserOffset) then
			BEGIN
				APImsg:=JAMAPIMSG_SEEKERROR;
				exit;
			END;

		{Write record}
		if (WriteFile(LrdHandle, LastRead, sizeof(JAMLREAD))<>sizeof(JAMLREAD)) then
			BEGIN
				APImsg:=JAMAPIMSG_CANTWRFILE;
				exit;
			END;

		{Update header info if told to}
		if (UpdateHdrInfo and not UpdHdrInfo(TRUE)) then
			exit;

		APImsg:=JAMAPIMSG_NOTHING;
		StoreLastRead:=TRUE;
	END;

END.

(* end of file "jammb.pas" *)

