
                  JAMAPI Programmer's Documentation
                                  by
                           Michael Lecuyer

                            based on the 

                                 JAM

           (The Joaquim-Andrew-Mats Message Base Proposal)

                   Copyright Michael Lecuyer (1993)


   The JAM message base is perhaps the closest electronic bulletin
boards can come to a universal message database.  It's predecessor,
the Hudson Message Base, enjoyed a certain popularity but it's
limitations soon undermined it's following.  The JAM message base
with it's virtually limitless size (limitless by the standards of
1994) is gaining favour among BBS and BBS door authors.

   The application programmers interface (API) is a complete library
that has been ported to numerous compilers.  The portability of the
JAM base code and it's ready availability to programmers is one of
the most promising aspects for the JAM base itself.  I have ported it
to the Watcom (tm) C/C++ 32 bit compiler in a matter of hours.  The
time was largely spent learning how to use the compiler.  The
programming of the API was very careful and model independent,
compiling as well for the 32 bit compiler where an integer is 64 bits
as for the 16 bit compilers with 16 bit integer.  No code changes
were necessary to accommodate the change.  For machines that do not
flip upper and lower bytes of integers porting will be a slightly
greater problem.

   An API is often divided into two portions - the data definitions
and the functions definitions.  Using this natural division the data
will be presented first.


   Basic to the system is an all-purpose structure lugged around
through most of the API functions.  Each concurrently open JAM base
requires their own instance of the JAMAPIREC.

This is the JAM API record:

typedef struct _JAMAPIREC
    {
    CHAR8           BaseName[200];  /* Path for message base */
    CHAR8ptr        WorkBuf;        /* Workspace (initialized by user) */
    UINT32          WorkLen;        /* Length of workspace (see above) */
    UINT32          WorkPos;        /* Current position, for multi-reads */
    INT16           isOpen;         /* Is open */
    INT16           HaveLock;       /* .JHR file is locked */
    int             Errno;          /* Last value of errno in case of error */
    int             APImsg;         /* API informative message */
    FHANDLE         HdrHandle;      /* File handle for .JHR file */
    FHANDLE         TxtHandle;      /* File handle for .JDT file */
    FHANDLE         IdxHandle;      /* File handle for .JDX file */
    FHANDLE         LrdHandle;      /* File handle for .JLR file */
    UINT32          LastMsgNum;     /* Last message number fetched */
    JAMIDXREC       Idx;            /* Message index */
    JAMHDR          Hdr;            /* Message header */
    JAMHDRINFO      HdrInfo;        /* Message header info */
    JAMSUBFIELDptr  SubFieldPtr;    /* Pointer to Subfield structure */
    UINT32          LastLRDnum;     /* Last LastRead record fetched (0-based) */
    JAMLREAD        LastRead;       /* LastRead (workspace) */

    /* Functions */

    FHANDLE (_JAMPROC * CreateFunc)(struct _JAMAPIREC _JAMDATA *, CHAR8ptr FileName);
    FHANDLE (_JAMPROC * OpenFunc)(struct _JAMAPIREC _JAMDATA *, CHAR8ptr FileName);
    int (_JAMPROC * CloseFunc)(struct _JAMAPIREC _JAMDATA *, FHANDLE Handle);
    INT32 (_JAMPROC * ReadFunc)(struct _JAMAPIREC _JAMDATA *, FHANDLE Handle, VOIDptr Buffer, INT32 Length);
    INT32 (_JAMPROC * WriteFunc)(struct _JAMAPIREC _JAMDATA *, FHANDLE Handle, VOIDptr Buffer, INT32 Length);
    INT32 (_JAMPROC * SeekFunc)(struct _JAMAPIREC _JAMDATA *, FHANDLE Handle, int FromWhere, INT32 Offset);
    int (_JAMPROC * LockFunc)(struct _JAMAPIREC _JAMDATA *, int DoLock);
    int (_JAMPROC * UnlinkFunc)(struct _JAMAPIREC _JAMDATA *, CHAR8ptr FileName);
    }
    JAMAPIREC, _JAMDATA * JAMAPIRECptr;


Programmers will be concerned primarily with the elements BaseName,
Errno, APImsg, JAMIDXREC (message index record), JAMHDR (jam message
record), JAMLREAD (last read message number), and to a lesser extent
JAMHDRINFO (information on the particular JAM base).

   Indirectly WorkLen has significance too.  Most of the structure is
maintained internally by the API.

   The BaseName is the path to the JAM base files without an
extension.  For example, the contents could be the C string:
"\\JAM\\FIDONET\\TELIX".  Extensions of ".JHR", ".JDT", ".JDX" and
".JLR" are within the control of the JAMAPI and need not be supplied.

   Errno is the DOS error number returned from a function.  When a
JAMAPI function is successful it will be 0.  In the case of a failure
reported by the operating system (a bad read for example) Errno will
be the OS error value.  These can be looked up in the appropriate OS
manual.

   APImsg is a value found in "JAMMB.H" and the table is presented
here:

   JAMAPIMSG_NOTHING       0   No error, normal return
   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

These are error numbers generated by the JAMAPI when there's an error
within the JAMAPI's control.


The JAMAPI record contains a number of structures:


   The JAMIDXREC is the index record for the message and contains
information to enable messages to an individual to be located
directly.  The index holds two fields - the CRC-32 of the destination
user name and the index into the header file (.JHR) where information
about the message is found.

typedef struct
    {
    UINT32  UserCRC;                   /* CRC-32 of destination username */
    UINT32  HdrOffset;                 /* Offset of header in .JHR file */
    }
    JAMIDXREC, _JAMDATA * JAMIDXRECptr;


   JAMHDR contains a record describing in detail many aspects of the
message.  The file pointer to the message, the person who sent it,
the person who will receive it, the subfield pointer, and the reply
tracking information are all kept here.  There is one record for each
message in the database.

typedef struct
    {
    CHAR8   Signature[4];              /* <J><A><M> followed by <NUL> */
    UINT16  Revision;                  /* CURRENTREVLEV */
    UINT16  ReservedWord;              /* Reserved */
    UINT32  SubfieldLen;               /* Length of subfields */
    UINT32  TimesRead;                 /* Number of times message read */
    UINT32  MsgIdCRC;                  /* CRC-32 of MSGID line */
    UINT32  ReplyCRC;                  /* CRC-32 of REPLY line */
    UINT32  ReplyTo;                   /* This msg is a reply to.. */
    UINT32  Reply1st;                  /* First reply to this msg */
    UINT32  ReplyNext;                 /* Next msg in reply chain */
    UINT32  DateWritten;               /* When msg was written */
    UINT32  DateReceived;              /* When msg was received/read */
    UINT32  DateProcessed;             /* When msg was processed by packer */
    UINT32  MsgNum;                    /* Message number (1-based) */
    UINT32  Attribute;                 /* Msg attribute, see "Status bits" */
    UINT32  Attribute2;                /* Reserved for future use */
    UINT32  TxtOffset;                 /* Offset of text in text file */
    UINT32  TxtLen;                    /* Length of message text */
    UINT32  PasswordCRC;               /* CRC-32 of password to access msg */
    UINT32  Cost;                      /* Cost of message */
    }
    JAMHDR, _JAMDATA * JAMHDRptr;


   The Status bits or Attribute bits are defined as:

    MSG_LOCAL       0x00000001L  Msg created locally
    MSG_INTRANSIT   0x00000002L  Msg is in-transit
    MSG_PRIVATE     0x00000004L  Private
    MSG_READ        0x00000008L  Read by addressee
    MSG_SENT        0x00000010L  Sent to remote
    MSG_KILLSENT    0x00000020L  Kill when sent
    MSG_ARCHIVESENT 0x00000040L  Archive when sent
    MSG_HOLD        0x00000080L  Hold for pick-up
    MSG_CRASH       0x00000100L  Crash
    MSG_IMMEDIATE   0x00000200L  Send Msg now, ignore restrictions
    MSG_DIRECT      0x00000400L  Send directly to destination
    MSG_GATE        0x00000800L  Send via gateway
    MSG_FILEREQUEST 0x00001000L  File request
    MSG_FILEATTACH  0x00002000L  File(s) attached to Msg
    MSG_TRUNCFILE   0x00004000L  Truncate file(s) when sent
    MSG_KILLFILE    0x00008000L  Delete file(s) when sent
    MSG_RECEIPTREQ  0x00010000L  Return receipt requested
    MSG_CONFIRMREQ  0x00020000L  Confirmation receipt requested
    MSG_ORPHAN      0x00040000L  Unknown destination
    MSG_ENCRYPT     0x00080000L  Msg text is encrypted
    MSG_COMPRESS    0x00100000L  Msg text is compressed
    MSG_ESCAPED     0x00200000L  Msg text is seven bit ASCII
    MSG_FPU         0x00400000L  Force pickup
    MSG_TYPELOCAL   0x00800000L  Msg is for local use only (not for export)
    MSG_TYPEECHO    0x01000000L  Msg is for conference distribution
    MSG_TYPENET     0x02000000L  Msg is direct network mail
    MSG_NODISP      0x20000000L  Msg may not be displayed to user
    MSG_LOCKED      0x40000000L  Msg is locked, no editing possible
    MSG_DELETED     0x80000000L  Msg is deleted


   JAMHDRINFO holds information about the particular JAM base
including the creation date, number of active messages and the first
message number.  The latter is necessary when calculating a message
number for additional messages.  Knowing that the JAMHDR file size is
directly proportional to the number of messages the number of a new
message becomes:

                   (size of the .JDX file)
    NewMsgNum =  ---------------------------  + BaseMsgNum
                 (the size of a .JDX record)

typedef struct
    {
    CHAR8   Signature[4];              /* <J><A><M> followed by <NUL> */
    UINT32  DateCreated;               /* Creation date */
    UINT32  ModCounter;                /* Last processed counter */
    UINT32  ActiveMsgs;                /* Number of active (not deleted) msgs */
    UINT32  PasswordCRC;               /* CRC-32 of password to access */
    UINT32  BaseMsgNum;                /* Lowest message number in index file */
    CHAR8   RSRVD[1000];               /* Reserved space */
    }
    JAMHDRINFO, _JAMDATA * JAMHDRINFOptr;


   JAMLREAD keeps the information external readers need to keep track
of how much a particular user has read of the JAM base.  Each record
tracks the reading habits on one user.

typedef struct
    {
    UINT32  UserCRC;                   /* CRC-32 of user name (lowercase) */
    UINT32  UserID;                    /* Unique UserID */
    UINT32  LastReadMsg;               /* Last read message number */
    UINT32  HighReadMsg;               /* Highest read message number */
    }
    JAMLREAD, _JAMDATA * JAMLREADptr;


   Subfield information isn't actually kept as a particular structure
but a pointer is maintained into a work buffer.

typedef struct
    {
    UINT16  LoID;                      /* Field ID, 0 - 0xffff */
    UINT16  HiID;                      /* Reserved for future use */
    UINT32  DatLen;                    /* Length of buffer that follows */
    CHAR8   Buffer[1];                 /* DatLen bytes of data */
    }
    JAMSUBFIELD, _JAMDATA * JAMSUBFIELDptr;

API function prototypes, explanations, and examples:


JAMsysCrc32:

   First and foremost a note about CRC's.  The Cyclical Redundency
Checks are usually found in communications or data verification
programmes and not in message databases.  Typically the CRC is
calculated on a data block before the data is moved and recalculate
after the data arrives.  If the two independently calculated CRC's
are not the same there is an error in the data.  CRC's have the
property of being very sensitive not only to the content of the block
of data, but to the ordering (unlike checksums - which are only
sensitive to the content and completely ignore the order).

   What do CRC's have to do with message base?  Ideally each sender
and receiver of the message as well as each message will have a
unique system-wide identifier.  This means that Jack may send a note
to Jill and both names are unique on the system.  There is only one
Jack and one Jill.  For the purposes of reply chains between messages
each message must also have a unique identifier.  Typically these
names and identifiers are kept as strings and consequently are slow
to process - all characters must be compared until a mismatch
occurs.  For Jack and Jill this will be two characters before the
names differed.  To speed up this process a single comparison would
be faster than a succession of comparisons.  Finding the perfect
"hash" function to create single unique values from all the system
identifier strings is extremely difficult and time consuming.  Each
time there is an addition to the system name list a new perfect
"hash" function must be found.

   Usually a less than perfect "hash" function is found and
collisions are inevitable.  Collisions occur when two identifiers
have the same "hash" value.  In this case there must be a fall-back
check of the two original strings for a final arbitration. 
Fortunately, the CRC-32 provides a fast and effective way to provide
very good hashing of the system's identifiers.  Since it isn't
perfect, a matched hash is usually corroborated with an actual
comparison of the strings. Comparing CRC-32's is much faster than
comparing strings and in the few collisions that occur the cost of
the extra comparisons is acceptable.

   In the JAM bases names must be "normalized" by a conversion to
lower case prior to the CRC-32 calculation.  All CRC's start with the
value of -1.  Fields for which are empty but expect to hold a CRC-32
are set to -1.  It is possible for a calculated CRC-32 of a string
to equal -1 so assuming that -1 always means an empty field is naive.
Message identifiers should not be "normalized" since case differences
may be part of the differences between them.


The JAM function to perform CRC's is:

UINT32 _JAMPROC JAMsysCrc32(void _JAMFAR *pBuf, unsigned int len, UINT32 crc)

where pBuf is a pointer to the data buffer, the len is the length of
the data in bytes, and the crc is -1 initially.  If the entire block
is unavailable at one time the JAMsysCrc32 function may be called
with each part.  In this case the initial call to JAMsysCrc32 is made
with -1 and subsequent calls are made with the value returned from
the previous call.

e.g.

   char *Bufa = "Compost: Because a rind is a terrible thing";
   char *Bufb = " to waste";

   firstcrc = JAMsysCrc32(Bufa, strlen(Bufa), -1);
   fullcrc  = JAMsysCrc32(Bufb, strlen(Bufb), firstcrc);



JAMmbOpen:

int _JAMPROC JAMmbOpen(JAMAPIRECptr apirec)

   Open the message base for operations.  The function returns 1 if
it successfully opens the named message base and 0 if it does not.
The most common reason for failure is that the named message base
does not yet exist.  The common code fragment illustrating the use of
JAMmbOpen is:

   // Defines used throughout the following examples.
   #define TRUE  1
   #define FALSE 0

   JAMAPIREC JamRec;           // The JAM message base record.

   if(!JAMsysInitApiRec(&JamRec, jambase, WORKBUFSIZE))
   {
      return FALSE;  // Usually caused by out of memory condition.
   }

   // Open the JAMbase - if that fails try creating the message base.

   if (!JAMmbOpen(&JamRec))
   {
      if(!JAMmbCreate(&JamRec))  // Attempt to create the message base.
      {
         printf("Unable to create JAM base: %s, code: %d, errno: %d\n",
                 jambase, JamRec.APImsg, JamRec.Errno);
         JAMsysDeinitApiRec(&JamRec);

         return FALSE;
      }
   }


JAMmbCreate:

int _JAMPROC JAMmbCreate(JAMAPIRECptr apirec);

   Try to create a new message base using the pointer to the JAM
record structure.  Returns 1 on success, 0 on failure.  Reasons for
failure are probably best determined by examining the value of errno
and the JamRec.APImsg described in the data description section
above.  For an example of operation see JAMmbOpen().


JAMmbClose:

int _JAMPROC JAMmbClose(JAMAPIRECptr apirec)

   Close the JAM message base which has been opened by JAMmbOpen or
JAMmbCreate.  This is also implicitly called when
JAMsysDeinitApiRec() is called.  Returns 1 on success, 0 on failure. 
Failure to close a JAM base requires examination of errno in the
JAMAPIREC structure.


JAMmbUnlink:

int _JAMPROC JAMmbUnlink(JAMAPIRECptr apirec)

   Remove the message base named by the JAMAPIREC structure.  The
message base must be closed first under DOS.  Do not call this this
function after JAMsysDeinitApiRec() is called since the message base
name has been cleared.  Returns 1 on success, 0 on failure. Failures
are based on your OS errors.


JAMmbReIndex:

int _JAMPROC JAMmbReIndex(JAMAPIRECptr apirec);

   Reindex messages in a currently open JAM base.  The message base
must be locked before calling JAMmbReIndex().  Remember to unlock the
JAM base after the operation.  Returns 1 on success, 0 on failure. 
Failure will occur if the JAM base is not open, is not locked, or an
OS service routine fails.

   The function is used to re-create the ".JDX" file based on the
data in the ".JHR" header file.  This is handy when packing the text
and header portions of a message base using the ".JDX" file and then
turning around and recreating the ".JDX" file afterward. 

e.g.
        // lock the message base, get header info
        if (JAMmbLockMsgBase(&JamRec, TRUE) == 0)
        {
           JAMmbReIndex(&JamRec);
        }
        // Unlock the message base and save the information.
        JAMmbUnLockMsgBase(&JamRec, TRUE);

   The use of TRUE in the JAMmbLockMsgBase() and unlock functions is
explained under JAMmbUpdateHeaderInfo().



JAMmbUpdateHeaderInfo:

int _JAMPROC JAMmbUpdateHeaderInfo(JAMAPIRECptr apirec, int WriteIt)

   Update or retrieve the JAM base header information block block
(JAMHDRINFO).  A non-zero value for WriteIt will cause the block to
be written.  Zero will cause the block to be read.  The JAM base must
be locked for a write to take place.  This function is implicitly
called by JAMmbLockMsgBase() and JAMmbUnLockMsgBase() when their
WriteIt flag is non-zero.  Returns 1 upon success and 0 upon
failure.  Failures are caused by the JAM base not being locked during
a write request,  the JAM base being closed or an OS service routine
failure.

e.g.

     // This example is the functional equivalent of the example
     // for JAMmbReindex().

     // lock the message base, get header info
     if (JAMmbLockMsgBase(&JamRec, FALSE) == 0)
     {
        JAMmbUpdateHeaderInfo(&JamRec, FALSE)
        JAMmbReIndex(&JamRec);
        JAMmbUpdateHeaderInfo(&JamRec, TRUE)
     }
     // Unlock the message base
     JAMmbUnLockMsgBase(&JamRec, FALSE);



JAMmbFindField:

int _JAMPROC JAMmbFindField(JAMAPIRECptr apirec, UINT32 WhatField,
                            UINT32ptr Position)

   Find a specific SUBFIELD in the work buffer held by the JAMAPIREC.
WhatField is the subfield number, one of the JAMSFLD_... subfields.
Position is the the pointer into the subfield work buffer
(transparently maintained by the JAMAPI).  For the first search
Position is set to 0.  Subsequent searches use the value returned by
JAMmbFindField().

   Returns 1 if the field is found and 0 if the field is not found. 
Failure also occurs if there isn't enough room in the work buffer.

e.g.

// A test function to read through a JAM base and print the sender and
// receiver's names.
// We pass the name of the function in "jambase"
//
void test_jam(char *jambase)
{

   // Start up the JAM api - either open an existing base or start a new one.

   if(!JAMsysInitApiRec(&JamRec, jambase, WORKBUFSIZE))
      return;  // Caused by out of memory condition.

   // Open the JAMbase - if that fails try creating the message base.

   if (!JAMmbOpen(&JamRec))
   {
      return FALSE;
   }

   // Scan through the message base.  The direction is forward because
   // of the TRUE parameter (non-zero means forward)

   JAMmbScanForMsgHdr(&JamRec, JamRec.HdrInfo.BaseMsgNum, TRUE, search_func);

   jam_close();   // Close everything to keep things tidy
}


// For each message header print two subfields, the sender name
// and the receiver name
// By the definition of this function this is called with the JAM record.
// We assume that the JAM subfields will all be in the workbuf.
//
// Return with the instructions to keep returning the next header
//
int search_func(JAMAPIREC *jrec)
{
   char *sender = NULL;
   char *receiver = NULL;
   JAMSUBFIELDptr subptr;    // to shorten the reference
   UINT32 subpos;            // starting position

   // Note that we start each search with the subpos at zero.  There
   // is no real guarantee that one field will occur before the other
   // If you know your BBS software and find there is ordering capturing
   // subfields can be optimized somewhat by not resetting subpos to zero
   // after the first time

   subpos = 0;

   if (JAMmbFindField(jrec, JAMSFLD_SENDERNAME, &subpos))
   {
      // Save away the name in a safe place.
      subptr = jrec->SubFieldPtr;
      sender = malloc((size_t)subptr->DatLen + 1);
      strncpy(sender, subptr->Buffer, (size_t)subptr->DatLen);
      sender[(size_t)subptr->DatLen] = 0;
   }

   // reset subpos and get the receiver's name.
   subpos = 0;
   
   if (JAMmbFindField(jrec, JAMSFLD_RECVRNAME, &subpos))
   {
      subptr = jrec->SubFieldPtr;
      receiver = malloc((size_t)subptr->DatLen + 1);
      strncpy(receiver, subptr->Buffer, (size_t)subptr->DatLen);
      receiver[(size_t)subptr->DatLen] = 0;
   }
   
   printf("From: %s,  To: %s\n", sender, receiver);

   if (receiver != NULL)
      free(receiver);
   if (sender != NULL)
      free(sender);

   // Tell JAMmbScanForMsgHdr() that we want another message header
   return ScanMsgHdrNextHdr;
}

        
JAMmbAddField:

int _JAMPROC JAMmbAddField(JAMAPIRECptr apirec, UINT32 WhatField,
                            int First, unsigned int DatLen,
                            UINT32ptr Position, CHAR8ptr Data)

   Add a specific subfield to the work buffer held by the JAMAPIREC. 
On the first call the value of Position must be set to 0.  Subsequent
calls will use the previous Position value.  If First is non-zero the
current field is being continued.  This permits subfields of more
than 64K bytes to be added.  If First is zero the field is appended
to the existing data.  ID fields will be copied.  Normally First is
non-zero for short fields.

   Returns 1 on success or 0 if the requested field does not fit into
the work buffer.

e.g.

   char *subject = "JAM Experiments"
   char *from =    "Michael"
   UINT32 subfield_pos = 0;       // initialize this to 0.

   if(!JAMmbAddField(&JamRec, JAMSFLD_SUBJECT, TRUE, strlen(subject),
                              &subfield_pos, subject))
      return FALSE;

   if(!JAMmbAddField(&JamRec, JAMSFLD_SENDERNAME, 1, strlen(from),
                              &subfield_pos, from))
      return FALSE;


JAMmbFetchMsgHdr:

int _JAMPROC JAMmbFetchMsgHdr(JAMAPIRECptr apirec, UINT32 WhatMsg,
                              int WithSubFields)

   Get the header information from the ".JHD" file into the JAMHDR
record for a particular message.  The value WhatMsg holds the message
number requested and passing a non-zero value for WithSubFields will
cause the subfields associated with the message to be loaded.

   Returns 1 upon success and 0 upon failure.


JAMmbFetchMsgIdx:

int _JAMPROC JAMmbFetchMsgIdx(JAMAPIRECptr apirec, UINT32 WhatMsg)

   Get the index record for the message specified by WhatMsg.  The
function retrieves the contents of JAMIDXREC, namely the destination
user's name as a CRC and the offset into the Offset of header in
".JHR" file.  Although automatically called by JAMmbScanForMsgHdr()
reading through the index file can be faster when looking for a
particular caller's mail.  See JAMmbScanForMsgIdx() for automatic
scanning. Remember that the CRC-32 in the JAMIDXREC may map to more
than one name so be prepared to check the caller's name against the
appropriate subfield in the message header.

Returns 1 upon success and 0 upon failure.


JAMmbFetchNextMsgHdr:

int _JAMPROC JAMmbFetchNextMsgHdr(JAMAPIRECptr apirec, int WithSubFields)

   Similar to JAMmbFetchMsgIdx() this is used to read a sequence of
JAMIDXREC's in a loop.  It gets the index for the record following
the value held in APIREC->LastMsgNum.  If WithSubFields in non-zero
subfields are read in as well.

   Returns 1 upon success and 0 upon failure.


JAMmbFetchPrevMsgHdr:

int _JAMPROC JAMmbFetchPrevMsgHdr(JAMAPIRECptr apirec, int WithSubFields)

   Similar to JAMmbFetchMsgIdx() this is used to read a sequence of
JAMIDXREC's in reverse in a loop.  It gets the index for the record
preceding the value held in APIREC->LastMsgNum.  If WithSubFields in
non-zero subfields are read in as well.

   Returns 1 upon success and 0 upon failure.


JAMmbFetchMsgTxt:

int _JAMPROC JAMmbFetchMsgTxt(JAMAPIRECptr apirec, int FirstFetch)

   Once the message header is read the text can be brought into
memory.  The data is read into the area pointed to by apirec->WorkBuf
and the size of the read is restricted to the value of
apirec->WorkLen.  The actual length of the text is in
apirec->Hdr.TxtLen.  If FirstFetch is non-zero then the first part of
the text will be read into the text work buffer.  If on the next call
FirstFetch is zero another block of the message will be read into the
work buffer.  The message text is exhausted when the APImsg value is
JAMAPIMSG_NOMORETEXT.  A zero return from the function indicates
either the end of text or an error.  The APImsg value is more useful
in determining the end of text.

e.g.


int read_text(JAMAPIRECptr apirec)
{
   int first = 1;
   UINT32 blksize = min(apirec->Hdr.TxtLen, apirec->WorkLen);
   UINT32 textleft = apirec->Hdr.TxtLen;

   // Read until there is nothing left.

   while (JAMmbFetchMsgTxt(&JamRec, first))
   {
      UINT32 resid = min(blksize, textleft);

      if (apirec->APImsg != JAMAPIMSG_NOTHING)
      {
         if (apirec->APImsg == JAMAPIMSG_NOMORETEXT)
            break;   // no more text.

         return FALSE;  // some error forces us to quit
      }

      fwrite(apirec->WorkBuf, (size_t)resid, 1, stdout); // show the text
      fflush(stdout);

      textleft -= resid;
      first = 0;
   }
   return TRUE;
}



JAMmbScanForMsgHdr:

int _JAMPROC JAMmbScanForMsgHdr(JAMAPIRECptr apirec, UINT32 WhatMsg,
                        int ScanFwd, ScanMsgHdrComp UserCompare)

   Scan message headers starting at the specified number (WhatMsg).
For each header found, the function calls USERCOMPARE. The ScanFwd
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 1 if user
function returned ScanMsgHdrStop and zero otherwise.  See
JAMmbFindField() for an example of use.

The USERCOMPARE may return one of the following:
ScanMsgHdrStop          Stop scanning
ScanMsgHdrDiscard       Discard the header info and get some more subfields
ScanMsgHdrNextHdr       Get next header

ScanFwd is non-zero for forward scanning, zero for reverse scanning.
WhatMsg is the starting point for the scan.


JAMmbScanForMsgIdx:

int _JAMPROC JAMmbScanForMsgIdx(JAMAPIRECptr apirec, UINT32 WhatMsg,
                        int ScanFwd, ScanMsgIdxComp UserCompare)

   Scan the index records in the ".JDX" file starting at WhatMsg
message number.  The function provided by the programmer,
UserCompare(), is called after each index record is read.  Although
it doesn't appear to be a useful function in itself since the
information it reads has no message number associated with it the
JAMAPI maintains the value of LastMsgNum through the operation.
LastMsgNum will be set to the current message number and may be used
as a parameter to functions such as JAMmbFetchMsgHdr() within the
user function.  The ScanFwd parameter is non-zero for a forward scan,
zero for a reverse scan.  The UserCompare() function return either of
the following values:

ScanMsgIdxStop          Stop scanning
ScanMsgIdxNextMsg       Continue scanning

e.g.

   // Do a quick scan to see if there is any mail for a particular
   // person past a certain date.  Calculate the CRC of the caller's
   // name and save both so the search routine can find them.

   CallerName = "Michael Lecuyer";
   CallerCRC = JAMsysCrc32(strlwr(CallerName), strlen(CallerName), (UINT32) -1L);
   CallerDate = 0;   // Since the date is 0 show all mail.

   // Set up the scan from the beginning of the message base.

   JAMmbScanForMsgIdx(&JamRec, JamRec.HdrInfo.BaseMsgNum, TRUE, search_idx_func);

...

// We are looking for all messages the to the caller since a certain
// date.  
//
int search_idx_func(JAMAPIREC *jrec)
{
   // If the CRC's match we must check that the actual names match.

   if (jrec->Idx.UserCRC == CallerCRC)
   {
      // get the message header without subfields (saves time)
      // We will compare the date the caller gave us vs. the date in the
      // message header.
      
      JAMmbFetchMsgHdr(jrec, jrec->LastMsgNum, FALSE);

      // if the date the message was written is later than the caller's
      // specified date write out the message number.

      if (jrec->Hdr.DateWritten > CallerDate)
         printf("Msg #%d\n", jrec->LastMsgNum);
   }

   return ScanMsgIdxNextMsg;
}



JAMmbStoreMsgHdr:

int _JAMPROC JAMmbStoreMsgHdr(JAMAPIRECptr apirec, UINT32 WhatMsg)

   Store a message header for the message indicated by WhatMsg.  A
side effect which may be valuable is that the HdrHandle's file
pointer will be set to the end of the fixed length header ready for
subfield information to be written directly to the file.

   Returns 1 upon success and 0 upon failure.


JAMmbStoreMsgIdx:

int _JAMPROC JAMmbStoreMsgIdx(JAMAPIRECptr apirec, UINT32 WhatMsg)

   Store an index record for the message indicated by WhatMsg.  The
IdxHandle's position will be located at the end of the current index
record.  An example of use is found in the writejam() function at the
end of the manual.

   Returns 1 upon success and 0 upon failure.


JAMmbStoreMsgTxt:

int _JAMPROC JAMmbStoreMsgTxt(JAMAPIRECptr apirec);

   Writes the message text pointed to by WorkBuf out at the current
message header's text pointer position (TxtOffset). The text's length
is kept in TextLen.  The TxtHandle's file pointer will be placed at
the end of the written text.  There is an assumption that the entire
message text resides in the WorkBuf. To write out text not kept in
the internal work buffer use JAMmbStoreMsgTxtBuf() which is more
flexible.  This is the equivalent of:

JAMmbStoreMsgTxtBuf(jrec, jrec->WorkBuf, jrec->TextLen, TRUE);

   Returns 1 upon success and 0 upon failure.


JAMmbStoreMsgTxt:

int _JAMPROC JAMmbStoreMsgTxtBuf(JAMAPIRECptr apirec, CHAR8ptr Buffer,
                                  INT32 BufLen, int IsFirst)

   A much more adaptable version of JAMmbStoreMsgTxt().  This
function will allow the buffer and it's size to be determined by the
programmer.  As in JAMmbStoreMsgTxt() when the operation completes
the file pointer is left at the end of the text in the file.  The
IsFirst parameter if set will cause the function to seek to the file
postion specified in TxtOffset.  If IsFirst is zero the seek is not
made and text is appended to the previous write.

   Returns 1 upon success and 0 upon failure.

e.g.

   char msgbuf[100];
   unsigned int bytes;
   int isfirst = TRUE;  // initially we use the seek pointer

[
   // Somewhere before the JAMmbStoreMsgTxtBuf() we have to
   // write out the message header and the subfields.  This
   // is so that the message header is in the correct position.
   // If we use the following loop we won't know the correct size
   // of the text until after the message header has been written out.
   // Solution: save the header seek pointer away prior to writing
   // the message header:
   //   headerpos = tell(JamRec.HdrHandle);
   //   if(!JAMmbStoreMsgHdr(&JamRec, msg_number))
   //        return FALSE;
}

   // The first StoreTxt will use the seek position in TextOffset.

   while ((bytes = fread(msgbuf, 1, sizeof(msgbuf), infile))
   {
      JAMmbStoreMsgTxtBuf(jrec, msgbuf, bytes, isfirst);
[
      JamRec.Hdr.TxtLen += bytes = strlen(msgbuf);
]

      // Subsequent StoreTxts should write after the last position

      isfirst = FALSE;
   }

[
   // Then, after the text is written we re-write the header out
   //   lseek(JamRec.HdrHandle, headerpos, SEEK_SET);
   //   if(!JAMmbStoreMsgHdr(&JamRec, msg_number))
   //      return FALSE;
]



JAMmbLockMsgBase:

int _JAMPROC JAMmbLockMsgBase(JAMAPIRECptr apirec, int FetchHdrInfo)

   Locks message base. If FetchHdrInfo is non-zero the function reads
the header info block at the beginning of the header file. If the
operation will change the contents of JAM base in any way the header
information should be read in to keep the ModCounter updated
(ModCounter counts the number of transactions affecting the
database).  The header information must always be fetched if
JAMmbUnLockMsgBase() plans to update the header information.


   Returns 1 upon success and 0 upon failure.



JAMmbUnLockMsgBase:

int _JAMPROC JAMmbUnLockMsgBase(JAMAPIRECptr apirec, int UpdateHdrInfo)

   Unlocks message base. If UpdateHdrInfo is non-zero the function
also writes the header info block at the beginning of the header
file. If UpdateHdrInfo is non-zero any changes to JAMHDRINFO record
are preserved and the ModCounter is incremented automatically.  The
JAMHDRINFO record must be read in if an update is to be meaningful. 


   Returns 1 upon success and 0 upon failure.


JAMmbFetchLastRead:

int _JAMPROC JAMmbFetchLastRead(JAMAPIRECptr apirec, UINT32 UserID)

   Fetch the LastRead record (JAMLREAD record) for a particular
UserID.  The UserID is a unique number on the system for a BBS
caller.  The UserID is not the same as the CRC for the BBS caller. 
The JAMLREAD record holds both the UserID and UserCRC as well as the
highest numbered message read and the last message number read.

   Returns 1 upon success and 0 upon failure.


JAMmbStoreLastRead:

int _JAMPROC JAMmbStoreLastRead(JAMAPIRECptr apirec, int UpdateHdrInfo)

   Store the LastRead record (JAMLREAD record) and update the header
record JAMHDRINFO, including the incrementing of ModCounter.

   Returns 1 upon success and 0 upon failure.



JAMsysTime:

UINT32 _JAMPROC JAMsysTime(UINT32ptr pTime)

   Calculates the number of seconds that has passed from January 1,
1970 until the current date and time.  It places the elapsed time
into pTime and returns the value as well.



JAMsysLocalTime:

JAMTMptr _JAMPROC JAMsysLocalTime(UINT32ptr pt)

   Converts the specified number of seconds since January 1, 1970, to
the corresponding date and time converting


JAMsysMkTime:

UINT32 _JAMPROC JAMsysMkTime(JAMTMptr pTm)

   Calculates the number of seconds that has passed from January 1,
1970 until the specified date and time.

The JAMTMptr structure:

/*
**  Structure to contain date/time information
*/
typedef struct JAMtm
    {
    int     tm_sec,                    /* Seconds 0..59                   */
            tm_min,                    /* Minutes 0..59                   */
            tm_hour,                   /* Hour of day 0..23               */
            tm_mday,                   /* Day of month 1..31              */
            tm_mon,                    /* Month 0..11                     */
            tm_year,                   /* Years since 1900                */
            tm_wday,                   /* Day of week 0..6 (Sun..Sat)     */
            tm_yday,                   /* Day of year 0..365              */
            tm_isdst;                  /* Daylight savings time (not used)*/
    } JAMTM, _JAMDATA * JAMTMptr;



e.g.

   // Get the time of day and converted it into the time a message
   // was processed:

   UINT32 tm;

   JAMsysTime(&tm);
   JAMsysMkTime(JAMsysLocalTime(&tm));
   JamRec.Hdr.DateProcessed = tm;


New Tricks:

Here are a couple of new functions that are more appropriate for
finding non-unique Subfields.  Fidonet kludge lines, for example,
may occur in droves.  Here's a FindFirst, Findnext set for SubFields.


// New function:  A kind of findfirst, findnext.  They're slight
// extensions of the JAMmbFindField().  In fact two of the same
// arguments are used, the third position parameter is kept internally.
//

static UINT32 Position;
static UINT32 ThisField;
int _JAMPROC JAMmbFindFieldFirst(JAMAPIRECptr apirec, UINT32 WhatField)
{
   Position = 0;
	ThisField = WhatField;
   return JAMmbFindFieldNext(apirec);
}

int _JAMPROC JAMmbFindFieldNext(JAMAPIRECptr apirec)
{
   Position=JAMsysAlign(Position);
   if (Position>=apirec->WorkLen)  /* past end of workbuffer */
      return (0);

   while (1)
      {
      apirec->SubFieldPtr=(JAMSUBFIELD *) JAMsysAddPtr(apirec->WorkBuf, Position);
      if (apirec->SubFieldPtr->LoID==(UINT16)ThisField)
         {
         Position+=(apirec->SubFieldPtr->DatLen+(UINT32)sizeof(JAMBINSUBFIELD));

         /* Found it */
         return (1);
         }
      else
         /* Get next subfield */
         {
          Position+=(apirec->SubFieldPtr->DatLen+(UINT32)sizeof(JAMBINSUBFIELD));
          Position=JAMsysAlign(Position);
          if (Position>=apirec->WorkLen)
              return (0);
          }
       }/*while*/

   /* Dummy return to avoid warnings from some compilers */
   return (0);
}

