/***************************************************************************
 *                                                                         *
 *   WLMIMPC.C                                                             *
 *                                                                         *
 *   Copyright (c) 1997 Galacticomm, Inc.         All Rights Reserved.     *
 *                                                                         *
 *   Worldlink Messaging Client importer functions.                        *
 *                                                                         *
 *                                            - J. Alvrus   1/27/97        *
 *                                                                         *
 ***************************************************************************/

#include "gcomm.h"
#include "gme.h"
#include "galtext.h"
#include "wormsg.h"
#include "wlmutl.h"
#include "wlmstoru.h"
#include "wlmstorc.h"
#include "wlmmsgu.h"
#include "wlminsu.h"
#include "wlmforu.h"
#include "wlmexpc.h"
#include "wlmimpc.h"
#include "wlmcfgc.h"
#include "wlmcsmc.h"
#include "wlmcli.h"

#define FILREV "$Revision: 5 $"

#define IMPSQUELCH 1000            /* import error: sender squelched       */

GBOOL wlImpActive=FALSE;           /* have importer utils been initialized?*/
GBOOL wlImpBkgnd;                  /* use background importer?             */
GBOOL impInProg=FALSE;             /* Worldlink importing a message        */
INT impCurHops=0;                  /* "Hops=" value of current imported msg*/
CHAR *impOrgDate=NULL;             /* original date of imported message    */
GBOOL impDeleting=FALSE;           /* Worldlink deleting a message         */
GBOOL impModifying=FALSE;          /* Worldlink modifying a message        */

GBOOL audimp;                      /* audit importer activity?             */
GBOOL auderr;                      /* audit importer errors?               */

ULONG maxCtrlTime;                 /* max time to try to process ctrl msg  */

ULONG maxTicks;                    /* maximum importer time slice          */
ULONG minTicks;                    /* minimum importer time slice          */
ULONG rtFactor;                    /* response time reduction factor       */
ULONG chFullPct;                   /* % nterms representing full load      */

struct impinf {                    /* importer state info                  */
     INT taskstt;                  /*   importer task state                */
     INT pumpstt;                  /*   pumper state                       */
     INT taskid;                   /*   importer task ID                   */
     ULONG timeDebt;               /*   excess time used in importer task  */
     LONG ntot;                    /*   total messages imported            */
     LONG neml;                    /*   email messages imported            */
     LONG nfor;                    /*   forum messages imported            */
     LONG natt;                    /*   attachments imported               */
     STORFILE *storf;              /*   incoming store file                */
     struct globid gid;            /*   global ID for control messages     */
     ULONG ctrlTime;               /*   time started processing control msg*/
     INT hops;                     /*   message's current hop count        */
     CHAR orgDate[WLMDATESIZ];     /*   message's original date            */
     CHAR work[GMEWRKSZ];          /*   GME work area                      */
     struct message msg;           /*   GME message header                 */
     CHAR *text;                   /*   buffer for message text            */
     CHAR *appinf;                 /*   buffer for app-defined info        */
     CHAR att[GCMAXPTH];           /*   attachment for current message     */
     CHAR newtpc[TPCSIZ];          /*   new topic (for network modify)     */
     CHAR ctrlSender[MAXADR];      /*   sender of control message          */
} imp;

                                   /* importer task states                 */
#define IMP_NEXT    0              /*   read next message                  */
#define IMP_SEND    1              /*   importing message                  */

                                   /* pumper states                        */
#define PUMP_NONE   0              /*   not doing anything                 */
#define PUMP_IMPORT 1              /*   importing a message                */
#define PUMP_RD4DEL 2              /*   reading a message to cancel it     */
#define PUMP_DELMSG 3              /*   cancelling a message               */
#define PUMP_RD4MOD 4              /*   reading a message to modify it     */
#define PUMP_MODMSG 5              /*   modifying a message                */

CHAR *tvInetPrefix(VOID);
GBOOL impCflChk(VOID *work,USHORT forum,LONG msgid);
VOID impTask(INT taskid);
GBOOL impOpenStor(VOID);
GBOOL impCtrlMsg(GBOOL *needPump,const CHAR *hdrBuf,VOID *msgBuf,size_t msgLen);
ULONG impSlice(VOID);
VOID impFinMsg(VOID);
VOID impError(INT errCode);
VOID impNotify(const CHAR *to,const struct message *msg);
VOID impAudStart(VOID);
VOID impAudError(const CHAR *details);
VOID impAudAbort(VOID);
VOID impAudDone(VOID);
CHAR *fixImpAddr(CHAR *addr);
VOID cancelImp(VOID);
VOID clearImp(VOID);

GBOOL                              /*   returns FALSE if error             */
initImp(VOID)                      /* initialize importer utilities        */
{
     clearImp();
     wlImpBkgnd=ynopt(USEBGI);
     audimp=ynopt(AUDIMP);
     auderr=ynopt(AUDIERR);
     maxCtrlTime=lngopt(MAXCTIM,1,3600);
     maxTicks=(65536UL*numopt(IMAXTIM,1,32767))/1000;
     minTicks=(65536UL*numopt(IMINTIM,1,32767))/1000;
     rtFactor=numopt(IRSPTIM,1,32767);
     chFullPct=numopt(INCHAN,1,100);
     initINPfx(ACTINP);
     register_textvar("WLM_INET_PFX",tvInetPrefix);
     imp.text=alcmem(TXTLEN);
     imp.appinf=alcmem(TXTLEN);
     dclvda(TXTLEN);
     setcfl(impCflChk);
     rtkick(2,impStart);
     wlImpActive=TRUE;
     return(TRUE);
}

VOID
closeImp(VOID)                     /* shut down importer utilities         */
{
     if (wlImpActive) {
          if (imp.taskid >= 0) {
               impAudAbort();
          }
          cancelImp();
          free(imp.text);
          free(imp.appinf);
     }
     wlImpActive=FALSE;
}

CHAR *
tvInetPrefix(VOID)                 /* WorldLink's internet prefix textvar  */
{
     return(inpfx);
}

GBOOL
impCflChk(                         /* GME conflict checker                 */
VOID *work,                        /*   work area to check                 */
USHORT forum,                      /*   forum to check                     */
LONG msgid)                        /*   message ID to check                */
{
     (VOID)work;
     if (msgid == 0L) {
          switch (imp.pumpstt) {
          case PUMP_IMPORT:
               return(imp.msg.forum == forum);
          case PUMP_RD4DEL:
          case PUMP_DELMSG:
          case PUMP_RD4MOD:
          case PUMP_MODMSG:
               return(chkmycfl(imp.work,forum,msgid));
          }
     }
     return(FALSE);
}

VOID
impStart(VOID)                     /* start importing messages             */
{
     INT tries;

     if (imp.taskid < 0) {
          ASSERT(imp.storf == NULL);
          for (tries=0 ; tries < 2 ; ++tries) {
               if (isfile(inStor[impStorNum])) {
                    LOG("Found messages to import");
                    if (!impOpenStor()) {
                         return;
                    }
                    impAudStart();
                    ASSERT(imp.ntot == 0 && imp.neml == 0
                        && imp.nfor == 0 && imp.natt == 0);
                    ASSERT(imp.taskstt == IMP_NEXT);
                    ASSERT(imp.timeDebt == 0);
                    imp.taskid=initask(impTask);
                    return;
               }
               swapInStors();
          }
          rcvHighIdx=0;
     }
}

VOID
impStop(VOID)                      /* stop background importer             */
{
     if (imp.taskid >= 0) {
          impAudAbort();
          cancelImp();
     }
}

VOID
impTask(                           /* background mail importer             */
INT taskid)                        /*   task ID in use                     */
{
     INT ec;
     size_t msglen;
     ULONG timeSlice,begTime,usedTime,msgidx;

     (VOID)taskid;
     if ((timeSlice=impSlice()) == 0) {
          LOG("Importer called: No users, resetting debt and proceeding");
          imp.timeDebt=0;
     }
     else {
          LOG(spr("Importer called: dbt=%lu, slc=%lu, rsp=%d, liu=%d",
                  imp.timeDebt,timeSlice,rsptim,nliniu()));
          if (imp.timeDebt >= timeSlice) {
               imp.timeDebt-=timeSlice;
               LOG("Importer returning to make up debt");
               return;
          }
          begTime=hrtval()-imp.timeDebt;
     }
     switch (imp.taskstt) {
     case IMP_NEXT:
          if ((msglen=storNextMsg(imp.storf,wlMsgBuf,MSGBUFSIZ)) == 0) {
               if ((ec=storLastErr()) != STER_EOF) {
                    impAudError(spr("Error reading incoming message file: %d",
                                    ec));
               }
               LOG("Done importing, deleting store and swapping");
               storClose(imp.storf);
               imp.storf=NULL;
               unlink(inStor[impStorNum]);
               swapInStors();
               if (isfile(inStor[impStorNum])) {
                    if (impOpenStor()) {
                         break;
                    }
                    impAudAbort();
               }
               else {
                    impAudDone();
               }
               cancelImp();
               return;
          }
          else {
               msgidx=storLastIdx();
               LOG(spr("Msg to import:   idx=%lu",msgidx));
               if (!impPrepMsg(wlMsgBuf,msglen,makeInAtt(msgidx))) {
                    impFinMsg();
                    break;
               }
               imp.taskstt=IMP_SEND;
          }
     case IMP_SEND:
          if (!impPump()) {
               impFinMsg();
          }
     }
     if (timeSlice != 0) {
          usedTime=hrtval()-begTime;
          imp.timeDebt=(usedTime > timeSlice ? usedTime-timeSlice : 0L);
     }
     statUpdate();
}

GBOOL                              /*   returns TRUE opened                */
impOpenStor(VOID)                  /* open current store file              */
{
     if ((imp.storf=storOpen(inStor[impStorNum])) == NULL) {
          LOG(spr("Unable to open incoming message file: %s",inStor[impStorNum]));
          impAudError("Unable to open incoming message file");
     }
     return(imp.storf != NULL);
}

GBOOL                              /*   returns FALSE if error preparing   */
impPrepMsg(                        /* prepare a message for import         */
VOID *msgBuf,                      /*   buffer containing transit message  */
size_t msgLen,                     /*   length of buffer contents          */
const CHAR *attFile)               /*   attachment path+file name (if any) */
{
     GBOOL ctrlflg,attflg;
     CHAR numBuf[sizeof("1234567890")],wlForumName[MAXFNAM];

     *imp.att='\0';
     attflg=attFile != NULL && isfile(stlcpy(imp.att,attFile,GCMAXPTH));
     wlmGetSectStr(wlHdrBuf,WLMAXHDR,HDRSECT,msgBuf,msgLen);
     if (impCtrlMsg(&ctrlflg,wlHdrBuf,msgBuf,msgLen)) {
          return(ctrlflg);
     }
     imp.pumpstt=PUMP_IMPORT;
     imp.hops=atoi(getWLHeader(numBuf,sizeof(numBuf),WLH_HOPS,"0",wlHdrBuf));
     getWLHeader(imp.orgDate,WLMDATESIZ,WLH_DATE,"",wlHdrBuf);
     getWLHeader(wlForumName,MAXFNAM,WLH_FORUM,"",wlHdrBuf);
     if (!xltTransitMsg(msgBuf,msgLen,&imp.msg,imp.text,imp.appinf,attflg)) {
          return(FALSE);
     }
     fixImpAddr(imp.msg.from);
     fixImpAddr(imp.msg.to);
     if (imp.msg.forum == EMLID) {
          if (wlmIsPostmaster(imp.msg.to)) {
               impError(GMENFND);
               unlink(imp.att);
               return(FALSE);
          }
          if (worSquelched(STGLEN,imp.msg.from)) {
               impError(IMPSQUELCH);
               unlink(imp.att);
               return(FALSE);
          }
     }
     else if (worSquelched(STGLEN,imp.msg.from)
           || worSquelched(STGLEN,imp.msg.to)) {
          unlink(imp.att);
          return(FALSE);
     }
     if (attflg && (imp.msg.forum == EMLID || getAutoApv(wlForumName))) {
          imp.msg.flags|=FILAPV;
     }
     inigmerq(imp.work);
     if (*imp.appinf != '\0') {
          gmeSetAppInfo(imp.work,imp.appinf);
     }
     return(TRUE);
}

GBOOL                              /*   returns FALSE when done            */
impPump(VOID)                      /* pump message importer                */
{
     INT rc;

     switch (imp.pumpstt) {
     case PUMP_NONE:
          return(FALSE);
     case PUMP_IMPORT:
          impInProg=TRUE;
          impCurHops=imp.hops;
          impOrgDate=imp.orgDate;
          if ((rc=impmsg(imp.work,&imp.msg,imp.text,imp.att)) != GMEAGAIN) {
               LOG(spr("Imported msg:    rc=%d",rc));
               ++imp.ntot;
               if (imp.msg.forum == EMLID) {
                    ++imp.neml;
               }
               else {
                    ++imp.nfor;
               }
               if (imp.msg.flags&FILATT) {
                    ++imp.natt;
               }
               if (rc < GMEAGAIN) {
                    if (imp.msg.forum == EMLID) {
                         impError(rc);
                    }
                    if (rc != GMENFND) {
                         impAudError(spr("Import failed with code %d",rc));
                    }
               }
               else if (rc == GMEAFWD) {
                    impNotify(gmexinf(),&imp.msg);
               }
               else {
                    impNotify(imp.msg.to,&imp.msg);
               }
               imp.pumpstt=PUMP_NONE;
          }
          impInProg=FALSE;
          impCurHops=0;
          impOrgDate=NULL;
          break;
     case PUMP_RD4DEL:
          rc=gmeGReadGlob(imp.work,&imp.gid,&imp.msg,imp.text);
          if (rc > GMEAGAIN) {
               if (sameas(imp.ctrlSender,skppfx(imp.msg.from))) {
                    imp.ctrlTime=0;
                    imp.pumpstt=PUMP_DELMSG;
                    return(TRUE);
               }
          }
          if (rc != GMEAGAIN) {
               imp.pumpstt=PUMP_NONE;
               clsgmerq(imp.work);
          }
          break;
     case PUMP_DELMSG:
          if (imp.ctrlTime == 0) {
               imp.ctrlTime=time(NULL);
          }
          impDeleting=TRUE;
          if ((rc=gmeGDeleteMsg(imp.work)) != GMEAGAIN) {
               if (rc == GMEUSE && (time(NULL)-imp.ctrlTime) < maxCtrlTime) {
                    LOG("Delete got in-use, retrying");
                    rc=GMEAGAIN;
               }
               else {
#ifdef DEBUG
                    if (time(NULL)-imp.ctrlTime >= maxCtrlTime) {
                         LOG("Ran out of time for delete control message");
                    }
#endif // DEBUG
                    imp.pumpstt=PUMP_NONE;
                    clsgmerq(imp.work);
               }
          }
          impDeleting=FALSE;
          break;
     case PUMP_RD4MOD:
          rc=gmeGReadGlob(imp.work,&imp.gid,&imp.msg,vdatmp);
          if (rc > GMEAGAIN) {
               if (sameas(imp.ctrlSender,skppfx(imp.msg.from))) {
                    imp.ctrlTime=0;
                    stlcpy(imp.msg.topic,imp.newtpc,TPCSIZ);
                    imp.pumpstt=PUMP_MODMSG;
                    return(TRUE);
               }
          }
          if (rc != GMEAGAIN) {
               imp.pumpstt=PUMP_NONE;
               clsgmerq(imp.work);
          }
          break;
     case PUMP_MODMSG:
          if (imp.ctrlTime == 0) {
               imp.ctrlTime=time(NULL);
          }
          impModifying=TRUE;
          if ((rc=gmeGModifyMsg(imp.work,&imp.msg,imp.text)) != GMEAGAIN) {
               if (rc == GMEUSE && (time(NULL)-imp.ctrlTime) < maxCtrlTime) {
                    LOG("Modify got in-use, retrying");
                    rc=GMEAGAIN;
               }
               else {
#ifdef DEBUG
                    if (time(NULL)-imp.ctrlTime >= maxCtrlTime) {
                         LOG("Ran out of time for modify control message");
                    }
#endif // DEBUG
                    imp.pumpstt=PUMP_NONE;
                    clsgmerq(imp.work);
               }
          }
          impModifying=FALSE;
          break;
     }
     return(rc == GMEAGAIN);
}

GBOOL                              /*   returns TRUE if control message    */
impCtrlMsg(                        /* check for and handle control messages*/
GBOOL *needPump,                   /*   TRUE if caller needs to impPump()  */
const CHAR *hdrBuf,                /*   transit message header             */
VOID *msgBuf,                      /*   buffer containing transit message  */
size_t msgLen)                     /*   length of buffer contents          */
{
     CHAR *cp,*ctype;
     GBOOL flg;
     USHORT fid;

     (VOID)msgBuf;
     (VOID)msgLen;
     if (*(cp=getWLHeader(vdatmp,vdasiz,WLH_CONTROL,"",hdrBuf)) == '\0') {
          return(FALSE);
     }
     *needPump=FALSE;
     ctype=parseWord(&cp);
     getWLHeader(imp.ctrlSender,MAXADR,WLH_FROM,"",hdrBuf);
     if (sameas(CMSG_CANCEL,ctype)) {
          flg=xltGlobIDStr(skpwht(cp),&imp.gid);
          fid=getWLForumID(getWLHeader(vdatmp,vdasiz,WLH_FORUM,"",hdrBuf));
          if (flg && imp.gid.sysid != gmeSysID()
           && fid != EMLID && getNetCanIn(vdatmp)) {
               inigmerq(imp.work);
               inormrd(imp.work,"",fid,FIRSTM);
               imp.pumpstt=PUMP_RD4DEL;
               *needPump=TRUE;
               return(TRUE);
          }
     }
     else if (sameas(CMSG_MODIFY,ctype)) {
          flg=xltGlobIDStr(skpwht(cp),&imp.gid);
          fid=getWLForumID(getWLHeader(vdatmp,vdasiz,WLH_FORUM,"",hdrBuf));
          if (flg && imp.gid.sysid != gmeSysID()
           && fid != EMLID && getNetModIn(vdatmp)) {
               inigmerq(imp.work);
               inormrd(imp.work,"",fid,FIRSTM);
               getWLHeader(imp.newtpc,TPCSIZ,WLH_TOPIC,"",hdrBuf);
               wlmGetSectStr(imp.text,TXTLEN,TXTSECT,msgBuf,msgLen);
               imp.pumpstt=PUMP_RD4MOD;
               *needPump=TRUE;
               return(TRUE);
          }
     }
     unlink(imp.att);
     return(TRUE);
}

ULONG
impSlice(VOID)                     /* import time slice based on load      */
{
     ULONG n,retval;

     if ((n=nliniu()) == 0) {
          return(0);
     }
     retval=maxTicks-((n-1)*(maxTicks-minTicks))/((chFullPct*nterms)/100);
     if ((n=rsptim/rtFactor) < retval) {
          retval-=n;
          if (retval > maxTicks) {
               retval=maxTicks;
          }
          else if (retval < minTicks) {
               retval=minTicks;
          }
     }
     else {
          retval=minTicks;
     }
     return(retval);
}

VOID
impFinMsg(VOID)                    /* finished with a message              */
{
     imp.taskstt=IMP_NEXT;
     storDelMsg(imp.storf);
     unlink(imp.att);
}

VOID
impError(                          /* handle error importing               */
INT errCode)                       /*   GME error code                     */
{
     INT reason;
     CHAR tmpto[MAXADR],tmptpc[TPCSIZ];

     if (wlmIsPostmaster(imp.msg.from)) {
          return;
     }
     switch (errCode) {
     case GMENFND:
          reason=IENFND;
          break;
     case IMPSQUELCH:
          if (worSquelchedBoard) {
               reason=IESQS;
          }
          else {
               reason=IESQU;
          }
          break;
     default:
          reason=IEUNK;
          break;
     }
     setmbk(wlmb);
     formErrorMsg(vdatmp,IMPERR,reason,imp.orgDate,fixImpAddr(imp.msg.to),
                  imp.msg.topic);
     rstmbk();
     if (isfmtted(imp.text)) {
          stlcat(vdatmp,"\r",TXTLEN);
          cvt2asc(imp.text,vdatmp,TXTLEN-strlen(vdatmp));
     }
     else {
          stlcat(vdatmp,imp.text,TXTLEN);
     }
     stlcpy(tmpto,imp.msg.from,MAXADR);
     stlcpy(tmptpc,imp.msg.topic,TPCSIZ);
     memset(&imp.msg,0,sizeof(struct message));
     stlcpy(imp.msg.from,WLPOSTM,MAXADR);
     stlcpy(imp.msg.to,tmpto,MAXADR);
     stlcpy(imp.msg.topic,tmptpc,TPCSIZ);
     simpsnd(&imp.msg,vdatmp,NULL);
}

VOID
impNotify(                         /* notify online recipient of message   */
const CHAR *to,                    /*   actual recipient of message        */
const struct message *msg)         /*   header of message                  */
{
     const CHAR *from;

     if (islocal(to) && onsys(to) && !sameas(to,msg->from)) {
          setmbk(wlmb);
          from=msg->from;
          if (wlmIsNativeAddr(from)) {
               from=skppfx(from);
          }
          if (msg->forum == EMLID) {
               if (msg->flags&PRIMSG) {
                    prfmlt(PIMPNOT,from);
               }
               else {
                    prfmlt(EIMPNOT,from);
               }
          }
          else {
               prfmlt(FIMPNOT,from,getfnm(msg->forum));
          }
          rstmbk();
          injoth();
     }
}

VOID
impAudStart(VOID)                  /* audit start of importing             */
{
     if (audimp) {
          shocst("WL MESSAGE IMPORT START","Background importing started");
     }
}

VOID
impAudError(                       /* audit error while importing          */
const CHAR *details)               /*   details of error                   */
{
     if (auderr) {
          shocst("WL MESSAGE IMPORT ERROR",details);
     }
}

VOID
impAudAbort(VOID)                  /* audit abortion of import             */
{
     if (audimp) {
          shocst("WL MESSAGE IMPORT ABORTED",
                 "Msgs: %ld (%lde/%ldf), Files: %ld",
                 imp.ntot,imp.neml,imp.nfor,imp.natt);
     }
}

VOID
impAudDone(VOID)                   /* audit successful completion          */
{
     if (audimp) {
          shocst("WL MESSAGE IMPORT DONE",
                 "Msgs: %ld (%lde/%ldf), Files: %ld",
                 imp.ntot,imp.neml,imp.nfor,imp.natt);
     }
}

CHAR *                             /*   returns pointer to address         */
fixImpAddr(                        /* make address into local format       */
CHAR *addr)                        /*   address to convert                 */
{
     if (wlmIsPostmaster(addr)) {
          strcpy(addr,WLPOSTM);
     }
     else if (sameas(worDomain(),worNodeID(addr))) {
          stlcpy(addr,worUseridUnmake(addr),UIDSIZ);
     }
     else if (wlmIsInetAddr(addr)) {
          if (*inpfx != '\0') {
               subpfx(addr,inpfx);
          }
          else if (wlUseEmail) {
               subpfx(addr,wlpfx);
          }
     }
     return(addr);
}

VOID
cancelImp(VOID)                    /* cancel any ongoing importer activity */
{
     if (imp.storf != NULL) {
          storClose(imp.storf);
          imp.storf=NULL;
     }
     if (gmerqopn(imp.work)) {
          clsgmerq(imp.work);
     }
     if (imp.taskid >= 0) {
          mfytask(imp.taskid,NULL);
          imp.taskid=NOIDX;
     }
     clearImp();
}

VOID
clearImp(VOID)                     /* clear importer state                 */
{
     imp.taskstt=IMP_NEXT;
     imp.pumpstt=PUMP_NONE;
     imp.taskid=NOIDX;
     imp.timeDebt=0;
     imp.ntot=imp.neml=imp.nfor=imp.natt=0;
     imp.storf=NULL;
     memset(&imp.gid,0,sizeof(struct globid));
     memset(&imp.msg,0,sizeof(struct message));
     *imp.att='\0';
     *imp.orgDate='\0';
     *imp.ctrlSender='\0';
}

const CHAR *                       /*   pointer to internal buffer         */
statImp(VOID)                      /* get importer status                  */
{
     INT status;

     setmbk(wlmb);
     clrprf();
     if (wlImpBkgnd) {
          if (imp.taskid < 0) {
               status=STIDLE;
          }
          else {
               switch (imp.taskstt) {
               case IMP_NEXT:
                    status=STIMPRD;
                    break;
               case IMP_SEND:
                    status=STIMPSND;
                    break;
               default:
                    status=STERROR;
                    break;
               }
          }
     }
     else {
          status=STIMPDIS;
     }
     prfmsg(MODIMP);
     prf("\t");
     prfmsg(status);
     stpans(prfbuf);
     rstmbk();
     return(prfbuf);
}
