////////////////////////////////////////////////////////////////////////////
//
//  msgatt.cpp
//
//  handle forum messages and attachments arriving separately
//
//                                                  - Paul Roub  10/29/97
//
/////////////////////////////////////////////////////////////////////////////

#include  "gcomm.h"
#include  "msgatt.h"

#include <functional>
#include <list>
#include <map>
#include <vector>
using namespace std;

#define FILREV "$Revision: 6 $"       // revision marker for ASSERT()

class messageAndAttach
{
public:
     messageAndAttach(
     ULONG tmpid);

     messageAndAttach();

     messageAndAttach(
     const messageAndAttach &oldMna);

     messageAndAttach &
     operator =(
     const messageAndAttach &oldMna);

    ~messageAndAttach();

     void
     init(                         // initialize data members
     ULONG tmpid);                 //   temp message/file ID

     VOID
     setAttachName(                // set attachment name
     const CHAR *fname);           //   new name

     VOID
     messageAborted();             // remove dir/filename on message abortion

     GBOOL                         //   TRUE if attachment opened OK
     openFile();                   // open attachment file

     GBOOL                         //   TRUE if data added OK
     addFileData(                  // add data to our attachment
     const VOID *data,             //   data to add
     size_t bytes);                //   data size

     GBOOL                         //   TRUE if started & finished
     finishFile();                 // finish attachment storage

     GBOOL                         //   TRUE if data stored OK
     storeMessageData(             // set our message data
     GBOOL asReply,                //   reply?
     const CHAR *workarea,         //   GME work area
     const CHAR *text,             //   message text
     const CHAR *cclist,           //   CC list
     const message *mhdr);         //   message header

     GBOOL                         //   TRUE if message ready to send
     readyToSend();                // is message ready?

     INT                           //   GME codes
     sendMessage();                // send our message, if ready

     GBOOL                         //   TRUE if a name is defined
     hasAttachName();              // see if an attachment has been named

     FILE *                        //   attachment file handle, or NULL
     getFile();                    // get attachment FILE

     GBOOL                         //   TRUE if renamed OK
     renameFile(                   // rename attachment file
     const CHAR *fname);           //   new name

private:
     void
     copyIn(                       // copy data from another messageAndAttach
     const messageAndAttach &oldMna); //   old struct
     
     GBOOL                         //   TRUE if closed OK
     closeAttachment();            // close our attachment file

     ULONG m_tmpid;

     CHAR m_mywrkarea[GMEWRKSZ];
     CHAR m_txtbuf[16384];   // hack
     CHAR m_cclist[1024];
     CHAR m_attname[GCMAXFNM];
     CHAR m_attLocalName[ GCMAXPTH ];
     CHAR m_attDir[ GCMAXPTH ];
     struct message m_mhdr;

     GBOOL m_msgFinished;
     GBOOL m_fileStarted;
     GBOOL m_fileFinished;

     GBOOL m_asReply;

     FILE *m_attFile;
};

typedef map< ULONG, messageAndAttach, less< ULONG > > mnalist;
typedef mnalist::iterator mnalistp;

static mnalist mnaList;


static
messageAndAttach *
getMna(
ULONG tmpid);



GBOOL                              //   TRUE if attachment exists or is in progress
attachInProgress(                  // check for attachments with a given ID
ULONG tmpid)                       //   ID to check
{
     mnalistp mnap = mnaList.find( tmpid );

     GBOOL     hasRecord = (mnap != mnaList.end());
     GBOOL     hasFile = FALSE;

     if (hasRecord)
     {
          messageAndAttach *mna = getMna( tmpid );
          ASSERT( mna != NULL );

          if (mna != NULL) 
          {
               hasFile = mna->hasAttachName();
          }
     }

     return( hasRecord && hasFile );
}


GBOOL                              //   TRUE if upload started OK
startFile(                         // begin a file attachment upload
ULONG tmpid,                       //   temp message/file ID
const CHAR *fname)                 //   attachment file name
{
     messageAndAttach *mna = getMna( tmpid );
     GBOOL     started = FALSE;

     if (mna != NULL)
     {
          mna->setAttachName( fname );
          mna->openFile();
          started = TRUE;
     }

     return( started );
}

VOID
messageAborted(
ULONG tmpid)
{
     messageAndAttach *mna = getMna( tmpid );
     if ( mna != NULL )
     {
          mna->messageAborted();
     }
}     

GBOOL                              //   TRUE if upload finished OK
finishFile(                        // finish file attachment upload
ULONG tmpid)                       //   temp file/message id
{
     messageAndAttach *mna = getMna( tmpid );
     GBOOL     finished = FALSE;

     if (mna != NULL)
     {
          finished = mna->finishFile();
     }

//     shocst( "finishFile", "finishFile( %lu ) == %i",  tmpid, (int)finished );

     return( finished );
}


GBOOL                              //   TRUE if file renamed OK
renameFile(                        // rename an attachment file
ULONG tmpid,                       //   temp file/message ID
const CHAR *newName)               //   new name for attachment
{
     messageAndAttach *mna = getMna( tmpid );
     GBOOL     renamed = FALSE;

     if (mna != NULL)
     {
          renamed = mna->renameFile( newName );
     }

     return( renamed );
}

GBOOL                              //   TRUE if data added OK
addFileData(                       // process file attachment upload data
ULONG tmpid,                       //   temp file/message id
const VOID *data,                  //   data to process
size_t bytes)                      //   size of data
{
     messageAndAttach *mna = getMna( tmpid );
     GBOOL     added = FALSE;

     if (mna != NULL)
     {
          if (mna->hasAttachName())
          {
               added = mna->addFileData( data, bytes );
          }
     }

//     shocst( "addFileData", "addFileData( %lu, %i ) == %i",  tmpid, (int)bytes, (int)added );

     return( added );
}

GBOOL                              //   TRUE if message data stored OK
storeMessageData(                  // store forum message info
ULONG tmpid,                       //   temp file/message ID
GBOOL asReply,                     //   is this a reply?
const CHAR *workarea,              //   message work area
const CHAR *text,                  //   message text
const CHAR *cclist,                //   CC list
const message *mhdr)               //   message header
{
     messageAndAttach *mna = getMna( tmpid );
     GBOOL     added = FALSE;

     if (mna != NULL)
     {
          added = mna->storeMessageData( asReply, workarea, text, cclist, mhdr );
     }

//     shocst( "storeMessageData", "storeMessageData( %lu, %i ) == %i",  tmpid, (int)asReply, (int)added );

     return( added );
}


GBOOL                              //   TRUE if message data here and any attachment is complete
readyToSend(                       // check whether we have all data for a message
ULONG tmpid)                       //   temp message/file ID
{
     messageAndAttach *mna = getMna( tmpid );
     GBOOL     ready = FALSE;

     if (mna != NULL)
     {
          return( mna->readyToSend() );
     }

//     shocst( "readyToSend", "readyToSend( %lu ) == %i",  tmpid, (int)ready );

     return( ready );
}


INT                                //   GME codes
sendMessage(                       // send a message (and any attachment)
ULONG tmpid)                       //   temp message/file ID
{
     messageAndAttach *mna = getMna( tmpid );
     GBOOL     sent = FALSE;

     if ((mna != NULL) && (mna->readyToSend()))
     {
          sent = mna->sendMessage();
     }

//     shocst( "sendMessage", "sendMessage( %lu ) == %i",  tmpid, (int)sent );

     return( sent );
}


FILE *                             //   the attachment's handle or NULL
getFile(                           // get an in-progress attachments FILE pointer
ULONG tmpid)                       //   temp message/file ID
{
     messageAndAttach *mna = getMna( tmpid );
     FILE      *file = NULL;

     if (mna != NULL)
     {
          file = mna->getFile();
     }

//     shocst( "getFile", "getFile( %lu ) == %08X",  tmpid, (ULONG)file );

     return( file );
}


static
messageAndAttach *                 //   message/attach struct for this ID
getMna(                            // get or create an info structure for a message ID
ULONG tmpid)                       //   the ID
{
     mnalistp mnap = mnaList.find( tmpid );

     if (mnap == mnaList.end())
     {
          messageAndAttach mna( tmpid );

          mnaList[ tmpid ] = mna;

//          shocst( "getMna", "getMna( %lu ) == %i",  tmpid, 0 );
     }
     else
     {
//          shocst( "getMna", "getMna( %lu ) == %i",  tmpid, 1 );
     }

     ASSERT( mnaList.find( tmpid ) != mnaList.end() );

     return( &mnaList[ tmpid ] );
}


messageAndAttach::messageAndAttach()
{
     init( 0 );
}

messageAndAttach::messageAndAttach(
ULONG tmpid)
{
     init( tmpid );
}


void
messageAndAttach::init(            // initialize data members
ULONG tmpid)                       //   temp message/file ID
{
     m_tmpid = tmpid;

     memset( m_mywrkarea, 0, sizeof( m_mywrkarea ) );
     memset( m_txtbuf, 0, sizeof( m_txtbuf ) );
     memset( m_cclist, 0, sizeof( m_cclist ) );
     memset( m_attname, 0, sizeof( m_attname ) );
     memset( &m_mhdr, 0, sizeof( message ) );

     m_msgFinished = FALSE;
     m_fileStarted = FALSE;
     m_fileFinished = FALSE;

     m_attFile = NULL;
     m_asReply = FALSE;
}

messageAndAttach::messageAndAttach(
const messageAndAttach &oldMna)
{
     copyIn( oldMna );
}

messageAndAttach &
messageAndAttach::operator =(
const messageAndAttach &oldMna)
{
     if (&oldMna != this)
     {
          copyIn( oldMna );
     }

     return( *this );
}

void
messageAndAttach::copyIn(          // copy data from another messageAndAttach
const messageAndAttach &oldMna)    //   old struct
{
     ASSERT( &oldMna != this );

     m_tmpid = oldMna.m_tmpid;

     memcpy( m_mywrkarea, oldMna.m_mywrkarea, sizeof( m_mywrkarea ) );
     memcpy( m_txtbuf, oldMna.m_txtbuf, sizeof( m_txtbuf ) );
     memcpy( m_cclist, oldMna.m_cclist, sizeof( m_cclist ) );
     memcpy( m_attname, oldMna.m_attname, sizeof( m_attname ) );
     memcpy( m_attLocalName, oldMna.m_attLocalName, sizeof( m_attLocalName ) );
     memcpy( m_attDir, oldMna.m_attDir, sizeof( m_attDir ) );

     m_mhdr = oldMna.m_mhdr;

     m_msgFinished = oldMna.m_msgFinished;
     m_fileStarted = oldMna.m_fileStarted;
     m_fileFinished = oldMna.m_fileFinished;

     m_asReply = oldMna.m_asReply;

     m_attFile = oldMna.m_attFile;
}

messageAndAttach::~messageAndAttach()
{
     if (m_fileStarted && ! m_fileFinished)
     {
          closeAttachment();
     }
     if (m_fileStarted)
     {
          remove( m_attLocalName );
          rmdir( m_attDir );
     }
}

VOID
messageAndAttach::setAttachName(   // set attachment name
const CHAR *fname)                 //   new name
{
     stlcpy( m_attname, fname, sizeof( m_attname ) );
     sprintf( m_attDir, "%s\\%lu", AHUPDIR, m_tmpid );
     sprintf( m_attLocalName, "%s\\%s", m_attDir, m_attname );
}


GBOOL                              //   TRUE if renamed OK
messageAndAttach::renameFile(      // rename attachment file
const CHAR *fname)                 //   new name
{
     GBOOL     renamed = FALSE;

     CHAR newAttName[ GCMAXPTH ];
     CHAR newAttDir[ GCMAXPTH ];
     CHAR newLocalName[ GCMAXPTH ];

     stlcpy( newAttName, fname, sizeof( newAttName ) );
     sprintf( newAttDir, "%s\\%lu", AHUPDIR, m_tmpid );
     sprintf( newLocalName, "%s\\%s", newAttDir, newAttName );

     if (isfile( newLocalName ))
     {
          unlink( newLocalName );
     }

     if (! isfile( newLocalName ))
     {
          renamed = (rename( m_attLocalName, newLocalName ) == 0);
     }

     stlcpy( m_attname, newAttName, sizeof( m_attname ) );
     stlcpy( m_attDir, newAttDir, sizeof( m_attDir ) );
     stlcpy( m_attLocalName, newLocalName, sizeof( m_attLocalName ) );

     return( renamed );
}


GBOOL                              //   TRUE if attachment opened OK
messageAndAttach::openFile()       // open attachment file
{
     if (m_attFile == NULL)
     {
          if (! isdir( AHUPDIR ))
          {
               mkdir( AHUPDIR );
          }

          if (! isdir( m_attDir ))
          {
               mkdir( m_attDir );
          }

          m_attFile = fopen( m_attLocalName, "wb" );
          if (m_attFile != NULL)
          {
               m_fileStarted = TRUE;
          }
     }

     return( m_attFile != NULL );
}


GBOOL                              //   TRUE if data added OK
messageAndAttach::addFileData(     // add data to our attachment
const VOID *data,                  //   data to add
size_t bytes)                      //   data size
{
     GBOOL     added = FALSE;

     if (m_attFile == NULL)
     {
          openFile();
     }

     if (m_attFile != NULL)
     {
          m_fileStarted = TRUE;

          size_t    written = fwrite( data, 1, bytes, m_attFile );

          added = (written == bytes);
     }

     return( added );
}


GBOOL                              //   TRUE if started & finished
messageAndAttach::finishFile()     // finish attachment storage
{
     GBOOL     finished = FALSE;

     if (m_attFile != NULL)
     {
          finished = closeAttachment();
     }

     return( finished );
}


GBOOL                              //   TRUE if data stored OK
messageAndAttach::storeMessageData( // set our message data
GBOOL asReply,                     //   reply?
const CHAR *workarea,              //   GME work area
const CHAR *text,                  //   message text
const CHAR *cclist,                //   CC list
const message *mhdr)               //   message header
{
     m_asReply = asReply;
     memcpy( m_mywrkarea, workarea, sizeof( m_mywrkarea ) );
     memcpy( m_txtbuf, text, sizeof( m_txtbuf ) );
     memcpy( m_cclist, cclist, sizeof( m_cclist ) );
     memcpy( &m_mhdr, mhdr, sizeof( message ) );
     m_msgFinished = TRUE;

     return( TRUE );
}

VOID
messageAndAttach::messageAborted()
{
     if (m_attLocalName[0] != '\0' )
     {
          unlink( m_attLocalName );
     }
     if (m_attDir[0] != '\0' )
     {
          rmdir( m_attDir );
     }
} 

GBOOL                              //   TRUE if message ready to send
messageAndAttach::readyToSend()    // is message ready?
{
     return( m_msgFinished &&
             (m_fileFinished || ! m_fileStarted)
           );
}


INT                                //   GME codes
messageAndAttach::sendMessage()    // send our message, if ready
{
     INT       sent = GMEERR;

     if (readyToSend())
     {
          const CHAR *attname = NULL;
          CHAR attbuf[ GCMAXPTH ];

          if (m_fileStarted)
          {
               ASSERT( m_fileFinished );
               
               if (valatt( m_mywrkarea, m_mhdr.from, m_mhdr.to, m_mhdr.forum ) == VALYES)
               {
                    stlcpy( m_mhdr.attname, m_attname, sizeof( m_mhdr.attname ) );
                    stlcpy( attbuf, ulname( &m_mhdr ), GCMAXPTH );

                    if (! movefile( m_attLocalName, attbuf ))
                    {
                         unlink( m_attLocalName );
                         rmdir( m_attDir );
                         return( GMEERR );
                    }

                    rmdir( m_attDir );
                    attname = attbuf;
                    m_mhdr.flags |= FILATT;
               }
          }

          if (m_asReply)
          {
               sent = reply( m_mywrkarea, &m_mhdr, m_txtbuf, attname, m_cclist );
          }
          else
          {
               sent = gmeSendMsg( m_mywrkarea, &m_mhdr, m_txtbuf, attname, m_cclist );
          }
     }

     return( sent );
}

GBOOL                              //   TRUE if a name is defined
messageAndAttach::hasAttachName()  // see if an attachment has been named
{
     return( strlen( m_attname ) > 0 );
}

GBOOL                              //   TRUE if closed OK
messageAndAttach::closeAttachment() // close our attachment file
{
     GBOOL     closed = FALSE;

     if (m_attFile != NULL)
     {
          fclose( m_attFile );
          m_fileFinished = TRUE;
          closed = TRUE;
     }

     return( closed );
}


FILE *                             //   attachment file handle, or NULL
messageAndAttach::getFile()        // get attachment FILE
{
     FILE      *result = NULL;

     if (m_fileStarted)
     {
          result = m_attFile;
     }

     return( result );
}


