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