XPSTREAM.CPP
/////////////////////////////////////////////////////////////////////////////// 
// 
//  File Name 
//      XPSTREAM.CPP  
// 
//  Description 
//      Wraps the StreamOnFile object to add buffering of the stream. 
//      The wrappered version uses an in-memory buffer, or cache, to 
//      reduce the number of actual Reads and Writes to the underlying 
//      stream.  The goal is, obviously, to improve performance. 
//      This code is not thread safe.  Also, the implementation 
//      is not optimized for streams that both read and write continuously 
//      because there will be too much flushing going on. 
// 
//  Author 
//      Irving De la Cruz 
// 
//  Note: This file is the C++ version of the buffered IStream wrapper 
//        developed originally for the C-based MSPEER transport in 
//        the MAPI SDK samples. 
// 
//  Revision: 1.7 
// 
// Written for Microsoft Windows Developer Support 
// Copyright (c) 1995-1996 Microsoft Corporation. All rights reserved. 
// 
#include "XPSTREAM.H" 
#include "TRACES.H" 
#include "COMWINDS.H" 
 
 
CCachedStream::CCachedStream (LPSTREAM pImpStream, ULONG ulFlags) 
{ 
    m_cRef = 1; 
    m_fDirty = FALSE; 
    m_ulFlags = ulFlags; 
    m_libBuff = 0; 
    m_cbBuffMac = 0; 
    m_pvCache = HeapAlloc (GetProcessHeap(), 0, XPSOF_BUFF_MAX); 
    if (NULL == m_pvCache) 
    { 
        throw CException (E_OUTOFMEMORY); 
    } 
    m_pImpStream = pImpStream; 
    m_pImpStream->AddRef(); 
} 
 
CCachedStream::~CCachedStream() 
{ 
    m_pImpStream->Release(); 
    if (m_pvCache) 
    { 
        HeapFree (GetProcessHeap(), 0, m_pvCache); 
    } 
} 
 
STDMETHODIMP CCachedStream::Read (LPVOID lpvData, ULONG cbSize, ULONG * lpcbRead) 
{ 
    HRESULT hResult = S_OK; 
    ULONG cbRead = 0; 
    ULONG cbT; 
    LPVOID lpvRead = NULL; 
 
    if (IsBadWritePtr (lpvData, cbSize) || (lpcbRead && IsBadWritePtr (lpcbRead, sizeof(ULONG)))) 
    { 
        hResult = STG_E_INVALIDPARAMETER; 
        goto ret; 
    } 
    if (!(XPSOF_READ & m_ulFlags)) 
    { 
        hResult = STG_E_ACCESSDENIED; 
        goto ret; 
    } 
    ASSERT (m_cbBuffMac >= m_libBuff); 
 
    // First, flush the buffer if it has been written into.  This 
    // operation empties our buffer and zeros the offset and size. 
    // We do this because we also buffer writes and we need to force 
    // the underlying stream to point to where the caller expects. 
    if ((XPSOF_WRITE & m_ulFlags) && m_fDirty) 
    { 
        hResult = Commit (0); 
        if (hResult) 
        { 
            goto ret; 
        } 
    } 
 
    // Determine if the buffer is empty (cbT == 0) or not (cbT != 0). 
    // We consider the buffer empty if we've read past the end of it 
    // or if m_cbBuffMac and m_libBuff are equal to zero. 
    cbT = m_cbBuffMac - m_libBuff; 
 
    // If the buffer is empty and the caller wants to read less than 
    // the size of our buffer, then we'll fill the buffer from the 
    // underlying stream object.  Adjust our buffer offset and size. 
    if (!cbT && (cbSize < XPSOF_BUFF_MAX)) 
    { 
        hResult = m_pImpStream->Read (m_pvCache, XPSOF_BUFF_MAX, &cbRead); 
        if (hResult) 
        { 
            goto ret; 
        } 
        m_libBuff = 0; 
        m_cbBuffMac = cbT = cbRead; 
    } 
 
    // Now, if the buffer is *not* empty and the caller wants to read 
    // fewer bytes than what is in the buffer, then we read it from 
    // our buffer, fix-up our offset, set the count read and leave. 
    if (cbT && (cbSize <= cbT)) 
    { 
        lpvRead = (LPVOID)((LPBYTE)m_pvCache + m_libBuff); 
        CopyMemory (lpvData, lpvRead, cbSize); 
        m_libBuff += cbSize; 
        cbRead = cbSize; 
        goto ret; 
    } 
 
    // If we are here, then the caller has requested more bytes to be 
    // read than what can fit in our buffer.  In this case, we copy 
    // the remaining data from the buffer (if any) into lpvData and 
    // then go straight to the underlying stream for the remainder. 
    // Either way, our buffer is empty after this operation. 
    lpvRead = lpvData; 
    if (cbT) 
    { 
        CopyMemory (lpvRead, (LPVOID)((LPBYTE)m_pvCache + m_libBuff), cbT); 
        lpvRead = (LPBYTE)lpvRead + cbT; 
        m_libBuff = 0; 
        m_cbBuffMac = 0; 
    } 
 
    hResult = m_pImpStream->Read (lpvRead, cbSize - cbT, &cbRead); 
    if (hResult) 
    { 
        goto ret; 
    } 
    cbRead += cbT; 
 
ret: 
    if (lpcbRead) 
    { 
        *lpcbRead = cbRead; 
    } 
    TraceResult ("CCachedStream::Read", hResult); 
    return hResult; 
} 
 
STDMETHODIMP CCachedStream::Write (const void * lpvData, ULONG cbSize, ULONG * lpcbWritten) 
{ 
    HRESULT hResult = S_OK; 
    ULONG cbWritten = 0; 
    ULONG cbT; 
    LPVOID lpvWrite = NULL; 
 
    if (IsBadReadPtr (lpvData, cbSize) || (lpcbWritten && IsBadWritePtr (lpcbWritten, sizeof(ULONG)))) 
    { 
        hResult = STG_E_INVALIDPARAMETER; 
        goto ret; 
    } 
    if (!(XPSOF_WRITE & m_ulFlags)) 
    { 
        hResult = STG_E_ACCESSDENIED; 
        goto ret; 
    } 
    ASSERT (m_cbBuffMac >= m_libBuff); 
 
    // First, if we've been Reading, then we need to re-wind the file 
    // pointer in the underlying stream to compensate for the last 
    // buffered Read.  Our new vacancy = the Max Size of our buffer. 
    if (!m_fDirty) 
    { 
        if (m_libBuff != m_cbBuffMac) 
        { 
            hResult = RewindStream (m_cbBuffMac - m_libBuff); 
            if (hResult) 
            { 
                goto ret; 
            } 
        } 
        m_libBuff = 0; 
        m_cbBuffMac = XPSOF_BUFF_MAX; 
    } 
 
    // Determine the total vacancy of the buffer. 
    cbT = m_cbBuffMac - m_libBuff; 
 
    // If the caller wants to Write more bytes than the current 
    // vacancy of the buffer, then commit the current buffer and 
    // Write the callers data directly to the stream.  If the 
    // buffer is not dirty, then the Commit call is a no-op. 
    if (cbSize > cbT) 
    { 
        hResult = Commit (0); 
        if (hResult) 
        { 
            goto ret; 
        } 
        hResult = m_pImpStream->Write (lpvData, cbSize, &cbWritten); 
        goto ret; 
    } 
 
    // The callers data will fit in our current buffer.  Copy the 
    // data into the buffer, mark the buffer as dirty, and adjust 
    // the buffer offset.  Set cbWritten to cbSize and return. 
    lpvWrite = (LPVOID)((LPBYTE)m_pvCache + m_libBuff); 
    CopyMemory (lpvWrite, lpvData, cbSize); 
    m_fDirty = TRUE; 
    m_libBuff += cbSize; 
    cbWritten = cbSize; 
 
ret: 
    if (lpcbWritten) 
    { 
        *lpcbWritten = cbWritten; 
    } 
    TraceResult ("CCachedStream::Write", hResult); 
    return hResult; 
} 
 
STDMETHODIMP CCachedStream::Seek (LARGE_INTEGER liMove, DWORD dwMode, ULARGE_INTEGER * lpliPos) 
{ 
    HRESULT hResult = S_OK; 
    if (lpliPos && IsBadWritePtr (lpliPos, sizeof(ULARGE_INTEGER))) 
    { 
        hResult = STG_E_INVALIDPARAMETER; 
        goto ret; 
    } 
    ASSERT (m_cbBuffMac >= m_libBuff); 
 
    // If our buffer is dirty, then we've been writing into it and 
    // we need to flush it.  Else, if it isn't dirty and our offset 
    // and buffer size are not equal, then we've been reading and we 
    // need to rewind the underlying stream to match our position. 
    if (m_fDirty) 
    { 
        hResult = Commit (0); 
        if (hResult) 
        { 
            goto ret; 
        } 
    } 
    else 
    { 
        if ((dwMode == STREAM_SEEK_CUR) && (m_libBuff != m_cbBuffMac)) 
        { 
            hResult = RewindStream (m_cbBuffMac - m_libBuff); 
            if (hResult) 
            { 
                goto ret; 
            } 
        } 
        m_libBuff = 0; 
        m_cbBuffMac = 0; 
    } 
    // Now, call the real stream's Seek method. 
    hResult = m_pImpStream->Seek (liMove, dwMode, lpliPos); 
ret: 
    TraceResult ("CCachedStream::Seek", hResult); 
    return hResult; 
} 
 
STDMETHODIMP CCachedStream::CopyTo (LPSTREAM lpStrmDst, 
                                    ULARGE_INTEGER cbCopy, 
                                    ULARGE_INTEGER * lpcbRead, 
                                    ULARGE_INTEGER * lpcbWritten) 
{ 
    HRESULT hResult; 
    if (IsBadReadPtr (lpStrmDst, sizeof(LPVOID)) || 
        IsBadWritePtr (lpcbRead, sizeof(ULARGE_INTEGER)) || 
        IsBadWritePtr (lpcbWritten, sizeof(ULARGE_INTEGER))) 
    { 
        hResult = STG_E_INVALIDPARAMETER; 
        goto ret; 
    } 
    ASSERT (m_cbBuffMac >= m_libBuff); 
 
    // If our buffer is dirty, then we've been writing into it and 
    // we need to flush it.  Else, if it isn't dirty and our offset 
    // and buffer size are not equal, then we've been reading and we 
    // need to rewind the underlying stream to match our position. 
    if (m_fDirty) 
    { 
        hResult = Commit (0); 
        if (hResult) 
        { 
            goto ret; 
        } 
    } 
    else 
    { 
        if (m_libBuff != m_cbBuffMac) 
        { 
            hResult = RewindStream (m_cbBuffMac - m_libBuff); 
            if (hResult) 
            { 
                goto ret; 
            } 
        } 
        m_libBuff = 0; 
        m_cbBuffMac = 0; 
    } 
 
    // Now, call the real streams CopyTo method. 
    hResult = m_pImpStream->CopyTo (lpStrmDst, cbCopy, lpcbRead, lpcbWritten); 
ret: 
    TraceResult ("CCachedStream::CopyTo", hResult); 
    return hResult; 
} 
 
STDMETHODIMP CCachedStream::Commit (ULONG ulFlags) 
{ 
    HRESULT hResult = S_OK; 
    // Flush my internal buffer if it is dirty. 
    if ((XPSOF_WRITE & m_ulFlags) && m_fDirty) 
    { 
        hResult = m_pImpStream->Write (m_pvCache, m_libBuff, NULL); 
        if (hResult) 
        { 
            goto ret; 
        } 
    } 
    // Mark my buffer as empty cal clean. 
    m_fDirty = FALSE; 
    m_libBuff = 0; 
    m_cbBuffMac = 0; 
ret: 
    TraceResult ("CCachedStream::Commit", hResult); 
    return hResult; 
} 
 
/////////////////////////////////////////////////////////////////////////////// 
//    CCachedStream::RewindStream() 
// 
//    Parameters 
//      ib      Number of bytes to rewind 
// 
//    Purpose 
//      This gets called to back-up the file pointer when a Write operation 
//      follows a Read operation.  This is necessary because the file pointer 
//      is actually further ahead in the file than the buffered file pointer. 
// 
//    Return Value 
//      An HRESULT 
// 
HRESULT WINAPI CCachedStream::RewindStream (ULONG ib) 
{ 
    if (ib) 
    { 
        LARGE_INTEGER liRewind; 
        liRewind.HighPart = 0xFFFFFFFF; 
        liRewind.LowPart = -((LONG)ib); 
        return m_pImpStream->Seek (liRewind, STREAM_SEEK_CUR, NULL); 
    } 
    return S_OK; 
} 
 
// End of File for XPSTREAM.CPP