XPLIST.CPP
/////////////////////////////////////////////////////////////////////////////// 
// 
//  File Name  
//      XPLIST.CPP 
// 
//  Description 
//      This file implements the classe CMsgQueue and CList used in this 
//      transport to process remote messages. The two classes help us process 
//      the list of messages selected in the Remote Viewer of a mail client. 
// 
//  Authors 
//      Les Thaler 
//      Irving De la Cruz 
// 
//  Revision: 1.7 
//   
// Written for Microsoft Windows Developer Support 
// Copyright (c) 1995-1996 Microsoft Corporation. All rights reserved. 
// 
#include "XPWDSR.H" 
 
enum 
{ 
    STAT, 
    EID, 
    NUM_PROPS 
}; 
static const SizedSPropTagArray (NUM_PROPS, sptProps) = 
{ 
    NUM_PROPS, 
    { 
        PR_MSG_STATUS, 
        PR_ENTRYID 
    } 
}; 
 
/////////////////////////////////////////////////////////////////////////////// 
//    CMsgQueue::CMsgQueue() 
// 
//    Parameters                         
//      None. 
// 
//    Purpose 
//      CMsgQueue class constructor.  
// 
//    Return Value 
//      None. 
// 
CMsgQueue::CMsgQueue() 
{ 
    m_ulItems  = 0; 
    m_pHead = NULL; 
    m_pTail = NULL; 
} 
 
/////////////////////////////////////////////////////////////////////////////// 
//    CMsgQueue::~MsgQueue() 
// 
//    Parameters                         
//      None.  
// 
//    Purpose 
//      CMsgQueue class destructor, walks the queue freeing each node 
//      and the node's members. 
// 
//    Return Value 
//      None 
// 
CMsgQueue::~CMsgQueue() 
{ 
    if (!Empty()) 
    { 
        PLIST_NODE pNode; 
        do      
        { 
            pNode = m_pHead->pNext; 
            delete m_pHead; 
            m_pHead = pNode; 
        } while (pNode); 
    } 
} 
 
/////////////////////////////////////////////////////////////////////////////// 
//    CMsgQueue::Insert() 
// 
//    Parameters                         
//      pNode       Pointer to the node to queue 
// 
//    Purpose 
//      Queues a node into the message queue 
// 
//    Return Value 
//      None. 
// 
void WINAPI CMsgQueue::Insert (PLIST_NODE pNode) 
{ 
    pNode->pNext = NULL; 
    if (Empty()) 
    { 
        m_pHead = m_pTail = pNode; 
    } 
    else 
    { 
        m_pTail->pNext = pNode; 
        m_pTail = m_pTail->pNext;            
    } 
    m_ulItems++; 
} 
 
/////////////////////////////////////////////////////////////////////////////// 
//    CMsgQueue::Delete() 
// 
//    Parameters                         
//      None. 
// 
//    Purpose 
//      Dequeues a node from the message queue. The caller is responsible for 
//      freeing the node after use. 
// 
//    Return Value 
//      The pointer to the dequeued node on success, NULL otherwise 
// 
PLIST_NODE WINAPI CMsgQueue::Delete() 
{    
    if (!m_ulItems) 
    { 
        return NULL; 
    } 
    PLIST_NODE pNode = m_pHead; 
    if (m_pHead->pNext == NULL) 
    { 
        m_pHead = m_pTail = NULL; 
    } 
    else 
    { 
        m_pHead = m_pHead->pNext; 
    } 
    m_ulItems--; 
    pNode->pNext = NULL; 
    return pNode; 
} 
 
/////////////////////////////////////////////////////////////////////////////// 
//    CList::CList() 
// 
//    Parameters                         
//      None 
// 
//    Purpose 
//      Class constructor for the CList class, the list of downloaded 
//      messages. Encapsulates the message queue and methods for manipulating 
//      in the context of the CXPLogon object. 
// 
//    Return Value 
//      None. 
// 
CList::CList() 
{ 
    m_pToDownload = NULL; 
    m_pDownloaded = NULL; 
    m_pLogon      = NULL; 
} 
  
/////////////////////////////////////////////////////////////////////////////// 
//    CList::~CList() 
// 
//    Parameters                         
//      None. 
// 
//    Purpose 
//      CList class destructor. Frees the TO DO and DONE queues 
// 
//    Return Value 
//      None. 
// 
CList::~CList() 
{ 
    // free the msg queues 
    delete m_pToDownload; 
    m_pToDownload = NULL; 
    delete m_pDownloaded; 
    m_pDownloaded = NULL; 
} 
 
/////////////////////////////////////////////////////////////////////////////// 
//    PropToMID() 
// 
//    Parameters                         
//      lProp       the MAPI MSGSTATUS_REMOTExx flag 
// 
//    Purpose 
//      Translate the MAPI MSGSTATUS_REMOTExx flag bimask into an internal 
//      message ID. This utility function is useful for walking the contents 
//      table and checking which messages have been marked for downloading, 
//      moving, etc. 
// 
//    Return Value 
//      The message ID if lProp indicates the message was marked for a remote 
//      operation, UNMARKED otherwise. 
// 
MID WINAPI PropToMID (long lProp) 
{ 
    if (lProp & MSGSTATUS_REMOTE_DOWNLOAD) 
    { 
        if (lProp & MSGSTATUS_REMOTE_DELETE) 
        { 
            return MSG_MOVE; 
        } 
        else 
        { 
            return MSG_DOWNLOAD; 
        } 
    } 
    else 
    { 
        if (lProp & MSGSTATUS_REMOTE_DELETE) 
        { 
            return MSG_DELETE; 
        } 
    } 
    return UNMARKED;         
} 
 
/////////////////////////////////////////////////////////////////////////////// 
//    CList::UpdateTableRow() 
// 
//    Parameters                         
//      pEID        Pointer to a message entry ID 
//      midAction   Action taken on a row of the remote folder contents table 
// 
//    Purpose 
//      Update a message's row in the contents table. Call this 
//      method after a remote DOWNLOAD, MOVE or DELETE to synch the local 
//      header contents table with what's really on the server. 
// 
//    Return Value 
//      TRUE on success, FALSE otherwise 
// 
BOOL WINAPI CList::UpdateTableRow (LPBYTE pEID, MID midAction) 
{ 
    SPropValue spvEID = { 0 }; 
    spvEID.ulPropTag = PR_ENTRYID; 
    spvEID.Value.bin.cb = TRANSPORT_MESSAGE_EID_SIZE; 
    spvEID.Value.bin.lpb = pEID; 
    LPTABLEDATA pTableData = m_pLogon->GetRemoteFolderTableData(); 
    HRESULT hResult; 
    if (midAction == MSG_DOWNLOAD) 
    { 
        LPSRow pRow; 
        ULONG i; 
        long lOldFlags; 
        hResult = pTableData->HrQueryRow (&spvEID, &pRow, NULL); 
        TraceResult ("CList::UpdateTableRow: Failed to query the table row", hResult); 
        if (!hResult) 
        { 
            for (i=0; i<pRow->cValues; i++) 
            { 
                if (PR_MESSAGE_FLAGS == pRow->lpProps[i].ulPropTag) 
                { 
                    lOldFlags = pRow->lpProps[i].Value.l; 
                    pRow->lpProps[i].Value.l |= MSGFLAG_READ; 
                    if (pRow->lpProps[i].Value.l != lOldFlags) 
                    { 
                        hResult = pTableData->HrModifyRow (pRow); 
                    } 
                    break; // Out of the FOR() loop 
                } 
            } 
            gpfnFreeBuffer (pRow); 
        } 
    } 
    else 
    { 
        hResult = pTableData->HrDeleteRow (&spvEID); 
    } 
    TraceResult ("CList::UpdateTableRow: Failed to update the table row", hResult); 
    return hResult; 
} 
 
/////////////////////////////////////////////////////////////////////////////// 
//    CList::Init() 
// 
//    Parameters                         
//      none. 
// 
//    Purpose 
//      Initializes the CList object. Creates the 'TO DO' and "DONE' 
//      message queues. 
// 
//    Return Value 
//      TRUE on success, FALSE otherwise 
// 
BOOL WINAPI CList::Init() 
{ 
    if (m_pToDownload) 
    { 
        delete m_pToDownload; 
    } 
    m_pToDownload = new CMsgQueue; 
    if (m_pDownloaded) 
    { 
        delete m_pDownloaded; 
    } 
    m_pDownloaded = new CMsgQueue; 
 
    if (!m_pToDownload || !m_pDownloaded) 
    { 
        delete m_pToDownload; 
        delete m_pDownloaded; 
        TraceMessage ("CList::Init: Failed to create download queues"); 
        return FALSE; 
    } 
    return TRUE; 
} 
 
/////////////////////////////////////////////////////////////////////////////// 
//    CList::DownLoadMsgs() 
// 
//    Parameters                         
//      pTable          Pointer to the header contents table 
//      ulRowCount      Count of entries in the contents table 
//      hPipe           Handle to the named pipe opened to the server 
//                      where the messages in being downloaded through. 
// 
//    Purpose 
//      Walks through the contents table and checks the PR_MSG_STATUS of each 
//      entry. Messages marked for download, move, or delete are allocated a 
//      node for queuing the "TO DO' queue. Messages marked for download or 
//      move additionally have a temporary filename created for them to hold 
//      the downloaded message.  
// 
//      If the resulting 'TO DO' queue is non-empty, the download worker 
//      thread is started and the CList object passed to it. This starts the 
//      background processing of messages in the 'TO DO' queue. 
// 
//    Return Value 
//      An HRESULT 
// 
HRESULT WINAPI CList::DownLoadMsgs (LPMAPITABLE pTable, ULONG ulRowCount, HANDLE hPipe) 
{ 
    HRESULT hResult = pTable->SetColumns ((LPSPropTagArray)&sptProps, 0); 
    if (hResult) 
    { 
        TraceResult ("CList::DownLoadMsgs: Failed to set the columns", hResult); 
        return hResult; 
    } 
 
    LPSRowSet pRows; 
    hResult = pTable->QueryRows (ulRowCount, 0, &pRows); 
    if (hResult) 
    { 
        TraceResult ("CList::DownLoadMsgs: Failed to get the rows", hResult); 
        return hResult; 
    } 
 
    // construct the list of msgs to download/delete/move 
    ASSERT (pRows->cRows); 
    MID midMsgAction; 
    for (ULONG i=0; i<pRows->cRows; i++) 
    { 
        ASSERT (PR_MSG_STATUS == pRows->aRow[i].lpProps[STAT].ulPropTag); 
        ASSERT (PR_ENTRYID == pRows->aRow[i].lpProps[EID].ulPropTag); 
        if (pRows->aRow[i].lpProps[STAT].ulPropTag == PR_MSG_STATUS && 
            pRows->aRow[i].lpProps[EID].ulPropTag  == PR_ENTRYID) 
        { 
            midMsgAction= PropToMID (pRows->aRow[i].lpProps[STAT].Value.l); 
            // Queue message only if marked for remote processing 
            if (UNMARKED != midMsgAction) 
            { 
                PLIST_NODE pNode= new LIST_NODE; 
                if (NULL == pNode) 
                { 
                    TraceMessage ("CList::DownLoadMsgs: Failed to allocate new node"); 
                    hResult = E_OUTOFMEMORY; 
                    break; 
                } 
                ZeroMemory (pNode, sizeof(LIST_NODE)); 
                pNode->fRetry = FALSE; 
 
                // Only downloaded/moved msgs need the tempfile 
                if (midMsgAction == MSG_DOWNLOAD || midMsgAction == MSG_MOVE)                 
                { 
                    m_pLogon->GetMsgTempFileName (pNode->szFileName); 
                } 
                ASSERT (TRANSPORT_MESSAGE_EID_SIZE == pRows->aRow[i].lpProps[EID].Value.bin.cb); 
                CopyMemory (pNode->Hdr.Info.EID, pRows->aRow[i].lpProps[EID].Value.bin.lpb, TRANSPORT_MESSAGE_EID_SIZE); 
                pNode->Hdr.ulMID = pNode->OpStat = midMsgAction; 
                // insert in 'TO DO' queue 
                m_pToDownload->Insert (pNode); 
            } 
        } 
    } 
    FreeProws(pRows); 
    if (!hResult) 
    { 
        ASSERT (!m_pToDownload->Empty()); 
        DownLoadNow (hPipe); 
    } 
    return hResult; 
} 
 
/////////////////////////////////////////////////////////////////////////////// 
//    CList::DownLoadNow() 
// 
//    Parameters                         
//      hPipe           Handle to the named pipe opened to the server 
//                      where the messages in being downloaded through. 
// 
//    Purpose 
//      For each message in the 'TO DO' queue, check its requested 
//      operation and format a control command to send over the pipe to 
//      the server.  
// 
//      The control command will contain the requested operation code and 
//      the remote entry ID of the message. The server will ACK the control 
//      command if the operation can be performed on the server side. The  
//      ACK message sent back by the server contains the size in bytes of 
//      the data stream that the server will next write to the pipe. 
// 
//      If the server can't perform the requested operation (e.g. the message 
//      can't be opened), it sends back an OP_FAILED command (NAK) and the  
//      dequeued TO DO message is discarded. This is considered a non-fatal error. 
//      and the rest of the queue is processed. 
//    
//      Downloaded and moved messages have a temporary file created for them before 
//      the data transfer to the message store is started. A failure creating the 
//      file is also non-fatal, the current message is discarded but processing 
//      continues. After the temp file is created, data transfer is started on 
//      the pipe. The stream is copied from the pipe into the temp file. 
// 
//      If the MOVE/DOWNLOAD operation succeeds, the 'TO DO' message is inserted 
//      into the 'DONE' queue which is then passed to StartMessage for processing 
//      in the spooler Poll() loop. 
// 
//      On exit, we send the server a 'HANG UP' command. 
// 
//    Return Value 
//      None 
// 
void WINAPI CList::DownLoadNow (HANDLE hPipe) 
{ 
    PLIST_NODE pNode; 
    MSG_HDR CtrlMsg, InMsg; 
    DWORD dwBytes; 
    m_ulMsgCount = m_pToDownload->m_ulItems; 
    for (ULONG i=0; i<m_ulMsgCount; i++) 
    { 
        pNode = m_pToDownload->Delete(); 
        if (NULL == pNode) 
        { 
            TraceMessage ("CList::DownLoadNow: Failed to get node from internal queue"); 
            continue; // The FOR() loop 
        } 
        CtrlMsg = pNode->Hdr; 
        switch (pNode->Hdr.ulMID) 
        { 
            case MSG_MOVE : 
            case MSG_DOWNLOAD : 
                // We need a temp file where we download the server message temporeraly 
                // until it gets picked up in IXPLogon::StartMessage() 
                pNode->hFile = CreateFile (pNode->szFileName, 
                                           GENERIC_READ | GENERIC_WRITE, 
                                           FILE_SHARE_READ, 
                                           NULL, 
                                           CREATE_ALWAYS, 
                                           FILE_ATTRIBUTE_TEMPORARY, 
                                           NULL); 
                if (INVALID_HANDLE_VALUE == pNode->hFile) 
                { 
                    pNode->OpStat = OP_FAILED; 
                    TraceResult ("CList::DownLoadNow: Failed to create local file", GetLastError()); 
                    goto ProcessNextMessage; 
                } 
                // The node has a header structure with the command action to instruct 
                // the server on what we need to do on this message 
                if (!WriteFile (hPipe, &CtrlMsg, sizeof(MSG_HDR), &dwBytes, NULL)) 
                { 
                    TraceResult ("CList::DownLoadNow: (MOVE/COPY) Failed to send control message to host", GetLastError()); 
                    goto ProcessNextMessage; 
                } 
                // Upon receiving the control header, the remote host sent a response 
                if (!ReadFile (hPipe, &InMsg, sizeof(MSG_HDR), &dwBytes, NULL)) 
                { 
                    TraceResult ("CList::DownLoadNow: (MOVE/COPY) Failed to receive message to data", GetLastError()); 
                    goto ProcessNextMessage; 
                } 
                // Check the result of the host and if successful, copy the message to a local file 
                if (OP_FAILED == InMsg.ulMID || 
                    NO_ERROR != FileCopy (pNode->hFile, hPipe, InMsg.Info.ulMsgLen)) 
                { 
                    goto ProcessNextMessage; 
                } 
                // If the message was moved from the remote server, we must remote the 
                // row in the remote folder viewer. 
                UpdateTableRow (pNode->Hdr.Info.EID, pNode->Hdr.ulMID); 
                m_pDownloaded->Insert (pNode); 
                pNode = NULL; 
                break; 
 
            case MSG_DELETE : 
                if (!WriteFile (hPipe, &CtrlMsg, sizeof(MSG_HDR), &dwBytes, NULL)) 
                { 
                    TraceResult ("CList::DownLoadNow: (DELETE) Failed to send control message to host", GetLastError()); 
                    goto ProcessNextMessage; 
                } 
                if (!ReadFile (hPipe, &InMsg, sizeof(MSG_HDR), &dwBytes, NULL)) 
                { 
                    TraceResult ("CList::DownLoadNow: (DELETE) Failed to receive message to data", GetLastError()); 
                    goto ProcessNextMessage; 
                } 
                if (InMsg.ulMID != OP_FAILED) 
                { 
                    UpdateTableRow (pNode->Hdr.Info.EID, MSG_DELETE); 
                } 
                break; 
            default : 
                TraceMessage ("CList::DownLoadNow: Unknown action on this node"); 
                break; 
        } 
ProcessNextMessage: 
        m_pLogon->UpdateProgress ((((i + 1) * 100) / m_ulMsgCount), REMOTE_ACTION_DOWNLOADING_MSGS); 
        if (pNode) 
        { 
            if (pNode->hFile) 
            { 
                CloseHandle (pNode->hFile); 
                DeleteFile (pNode->szFileName); 
            } 
            delete pNode; 
        } 
    } 
    CtrlMsg.ulMID = GOODBYE; 
    WriteFile (hPipe, &CtrlMsg, sizeof(MSG_HDR), &dwBytes, NULL); 
    m_ulMsgLeft = m_ulMsgCount;  
} 
 
/////////////////////////////////////////////////////////////////////////////// 
//    CList::UpdateProgress() 
// 
//    Parameters 
//      None 
// 
//    Purpose 
//      Update the progress percentage in the status row after a message is 
//      processed in IXPLogon::StartMessage() 
// 
//    Return Value 
//      None 
// 
void WINAPI CList::UpdateProgress() 
{ 
    if (m_ulMsgCount) 
    { 
        m_ulMsgLeft--; 
        m_pLogon->UpdateProgress ((((m_ulMsgCount - m_ulMsgLeft) * 100) / m_ulMsgCount), 
                                    REMOTE_ACTION_PROCESSING_MSGS); 
    } 
} 
 
/////////////////////////////////////////////////////////////////////////////// 
//    FileCopy() 
// 
//    Parameters                         
//      hDest       Handle of stream object receiving the data 
//      hSrc        Handle of stream object whence data 
//      dwMsgLen    Number of bytes to transfer 
// 
//    Purpose 
//      Transfers dwMsgLen bytes from hSrc to hDest. The objects can be 
//      open file handles, pipes, or any stream oriented object that uses 
//      the Read/WriteFile semantics.       
// 
//    Return Value 
//      NO_ERROR on success, ERROR_READ/WRITE_FAULT otherwise 
// 
long WINAPI FileCopy (HANDLE hDest, HANDLE hSrc, DWORD dwMsgLen) 
{ 
    BYTE abBuffer[IO_BUFFERSIZE];  
    DWORD dwRead, dwRemaining = dwMsgLen; 
    BOOL bSuccess; 
    long lResult = NO_ERROR; 
    for (DWORD dwWritten=0 ; dwRemaining>0; dwRemaining -= dwWritten) 
    { 
        bSuccess = ReadFile (hSrc, abBuffer, min(dwRemaining, IO_BUFFERSIZE), &dwRead, NULL); 
        if (!dwRead || !bSuccess) 
        { 
            lResult = ERROR_READ_FAULT; 
            break; 
        } 
        bSuccess = WriteFile (hDest, abBuffer, dwRead, &dwWritten, NULL); 
        if (!dwWritten || !bSuccess) 
        { 
            lResult = ERROR_WRITE_FAULT; 
            break; 
        } 
    } 
    if (lResult) 
    { 
        TraceResult ("CList::FileCopy: IO operation failed", GetLastError()); 
    } 
    else 
    { 
        FlushFileBuffers (hDest); 
        SetFilePointer (hDest, 0, NULL, FILE_BEGIN); 
    } 
    return lResult; 
} 
 
// End of file for XPLIST.CPP