gecko-dev/xpcom/ds/nsBuffer.cpp

368 lines
9.9 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* The contents of this file are subject to the Netscape Public License
* Version 1.0 (the "NPL"); you may not use this file except in
* compliance with the NPL. You may obtain a copy of the NPL at
* http://www.mozilla.org/NPL/
*
* Software distributed under the NPL is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
* for the specific language governing rights and limitations under the
* NPL.
*
* The Initial Developer of this code under the NPL is Netscape
* Communications Corporation. Portions created by Netscape are
* Copyright (C) 1998 Netscape Communications Corporation. All Rights
* Reserved.
*/
#include "nsBuffer.h"
#include "nsAutoLock.h"
#include "nsCRT.h"
#include "nsIInputStream.h"
#include "nsIServiceManager.h"
#include "nsIPageManager.h"
////////////////////////////////////////////////////////////////////////////////
nsBuffer::nsBuffer()
: mGrowBySize(0),
mMaxSize(0),
mAllocator(nsnull),
mBufferSize(0),
mReadSegment(nsnull),
mReadCursor(0),
mWriteSegment(nsnull),
mWriteCursor(0),
mEOF(PR_FALSE)
{
NS_INIT_REFCNT();
PR_INIT_CLIST(&mSegments);
}
NS_IMETHODIMP
nsBuffer::Init(PRUint32 growBySize, PRUint32 maxSize,
nsIAllocator* allocator)
{
mGrowBySize = growBySize;
mMaxSize = maxSize;
mAllocator = allocator;
NS_ADDREF(mAllocator);
return NS_OK;
}
nsBuffer::~nsBuffer()
{
NS_IF_RELEASE(mAllocator);
}
NS_IMPL_ISUPPORTS(nsBuffer, nsIBuffer::GetIID());
////////////////////////////////////////////////////////////////////////////////
nsresult
nsBuffer::PushWriteSegment()
{
nsAutoMonitor mon(this); // protect mSegments
if (mBufferSize >= mMaxSize) {
return NS_ERROR_FAILURE;
}
// allocate a new segment to write into
char* seg;
PRCList* header;
seg = (char*)mAllocator->Alloc(mGrowBySize);
if (seg == nsnull)
return NS_ERROR_OUT_OF_MEMORY;
mBufferSize += mGrowBySize;
header = (PRCList*)seg;
PR_INSERT_BEFORE(header, &mSegments); // insert at end
// initialize the write segment
mWriteSegment = seg;
mWriteSegmentEnd = mWriteSegment + mGrowBySize;
mWriteCursor = mWriteSegment + sizeof(PRCList);
return NS_OK;
}
nsresult
nsBuffer::PopReadSegment()
{
nsresult rv;
nsAutoMonitor mon(this); // protect mSegments
PRCList* header = (PRCList*)mSegments.next;
char* segment = (char*)header;
NS_ASSERTION(mReadSegment == segment, "wrong segment");
// make sure that the writer isn't still in this segment (that the
// reader is removing)
NS_ASSERTION(!(segment <= mWriteCursor && mWriteCursor < segment + mGrowBySize),
"removing writer's segment");
PR_REMOVE_LINK(header); // unlink from mSegments
mBufferSize -= mGrowBySize;
rv = mAllocator->Free(segment);
if (NS_FAILED(rv)) return rv;
// initialize the read segment
if (PR_CLIST_IS_EMPTY(&mSegments)) {
mReadSegment = nsnull;
mReadSegmentEnd = nsnull;
mReadCursor = nsnull;
}
else {
mReadSegment = (char*)mSegments.next;
mReadSegmentEnd = mReadSegment + mGrowBySize;
mReadCursor = mReadSegment + sizeof(PRCList);
}
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
// nsIBuffer methods:
NS_IMETHODIMP
nsBuffer::Read(char* toBuf, PRUint32 bufLen, PRUint32 *readCount)
{
nsresult rv;
PRUint32 readBufferLen;
char* readBuffer;
*readCount = 0;
while (bufLen > 0) {
rv = GetReadBuffer(&readBufferLen, &readBuffer);
if (rv == NS_BASE_STREAM_EOF) // all we're going to get
return *readCount > 0 ? NS_OK : NS_BASE_STREAM_EOF;
if (NS_FAILED(rv)) return rv;
if (readBufferLen == 0)
return mEOF && *readCount == 0 ? NS_BASE_STREAM_EOF : NS_OK;
PRUint32 count = PR_MIN(bufLen, readBufferLen);
nsCRT::memcpy(toBuf, readBuffer, count);
*readCount += count;
toBuf += count;
bufLen -= count;
if (mReadCursor + count == mReadSegmentEnd) {
rv = PopReadSegment();
if (NS_FAILED(rv)) return rv;
}
else {
mReadCursor += count;
}
}
return NS_OK;
}
NS_IMETHODIMP
nsBuffer::GetReadBuffer(PRUint32 *readBufferLength, char* *result)
{
if (mReadSegment == nsnull) {
if (PR_CLIST_IS_EMPTY(&mSegments)) {
*readBufferLength = 0;
*result = nsnull;
return mEOF ? NS_BASE_STREAM_EOF : NS_OK;
}
else {
mReadSegment = (char*)mSegments.next;
mReadSegmentEnd = mReadSegment + mGrowBySize;
mReadCursor = mReadSegment + sizeof(PRCList);
}
}
// snapshot the write cursor into a local variable -- this allows
// a writer to freely change it while we're reading while avoiding
// using a lock
char* snapshotWriteCursor = mWriteCursor; // atomic
// next check if the write cursor is in our segment
if (mReadCursor <= snapshotWriteCursor &&
snapshotWriteCursor < mReadSegmentEnd) {
// same segment -- read up to the snapshotWriteCursor
*readBufferLength = snapshotWriteCursor - mReadCursor;
}
else {
// otherwise, read up to the end of this segment
*readBufferLength = mReadSegmentEnd - mReadCursor;
}
*result = mReadCursor;
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
NS_IMETHODIMP
nsBuffer::Write(const char* fromBuf, PRUint32 bufLen, PRUint32 *writeCount)
{
nsresult rv;
if (mEOF)
return NS_BASE_STREAM_EOF;
*writeCount = 0;
while (bufLen > 0) {
PRUint32 writeBufLen;
char* writeBuf;
rv = GetWriteBuffer(&writeBufLen, &writeBuf);
if (NS_FAILED(rv)) {
// if we failed to allocate a new segment, we're probably out
// of memory, but we don't care -- just report what we were
// able to write so far
return NS_OK;
}
PRUint32 count = PR_MIN(writeBufLen, bufLen);
nsCRT::memcpy(writeBuf, fromBuf, count);
bufLen -= count;
*writeCount += count;
// set the write cursor after the data is valid
if (mWriteCursor + count == mWriteSegmentEnd) {
mWriteSegment = nsnull; // allocate a new segment next time around
mWriteSegmentEnd = nsnull;
mWriteCursor = nsnull;
}
else
mWriteCursor += count;
}
return NS_OK;
}
NS_IMETHODIMP
nsBuffer::Write(nsIInputStream* fromStream, PRUint32 *writeCount)
{
nsresult rv;
if (mEOF)
return NS_BASE_STREAM_EOF;
*writeCount = 0;
while (PR_TRUE) {
PRUint32 writeBufLen;
char* writeBuf;
rv = GetWriteBuffer(&writeBufLen, &writeBuf);
if (NS_FAILED(rv)) {
// if we failed to allocate a new segment, we're probably out
// of memory, but we don't care -- just report what we were
// able to write so far
return NS_OK;
}
PRUint32 readCount;
rv = fromStream->Read(writeBuf, writeBufLen, &readCount);
if (NS_FAILED(rv)) {
// if we failed to read just report what we were
// able to write so far
return NS_OK;
}
*writeCount += readCount;
// set the write cursor after the data is valid
if (mWriteCursor + readCount == mWriteSegmentEnd) {
mWriteSegment = nsnull; // allocate a new segment next time around
mWriteSegmentEnd = nsnull;
mWriteCursor = nsnull;
}
else
mWriteCursor += readCount;
}
return NS_OK;
}
NS_IMETHODIMP
nsBuffer::GetWriteBuffer(PRUint32 *writeBufferLength, char* *result)
{
if (mEOF)
return NS_BASE_STREAM_EOF;
nsresult rv;
if (mWriteSegment == nsnull) {
if (mBufferSize >= mMaxSize)
return NS_ERROR_FAILURE;
rv = PushWriteSegment();
if (NS_FAILED(rv)) return rv;
NS_ASSERTION(mWriteSegment != nsnull, "failed to allocate segment");
}
*writeBufferLength = mWriteSegmentEnd - mWriteCursor;
*result = mWriteCursor;
return NS_OK;
}
NS_IMETHODIMP
nsBuffer::SetEOF()
{
if (mEOF)
return NS_BASE_STREAM_EOF;
mEOF = PR_TRUE;
mWriteSegment = nsnull; // allows reader to free last segment w/o asserting
mWriteSegmentEnd = nsnull;
// don't reset mWriteCursor here -- we need it for the EOF point in the buffer
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
static NS_DEFINE_CID(kAllocatorCID, NS_ALLOCATOR_CID);
NS_BASE nsresult
NS_NewBuffer(nsIBuffer* *result,
PRUint32 growBySize, PRUint32 maxSize)
{
nsresult rv;
NS_WITH_SERVICE(nsIAllocator, alloc, kAllocatorCID, &rv);
if (NS_FAILED(rv)) return rv;
nsBuffer* buf = new nsBuffer();
if (buf == nsnull)
return NS_ERROR_OUT_OF_MEMORY;
rv = buf->Init(growBySize, maxSize, alloc);
if (NS_FAILED(rv)) {
delete buf;
return rv;
}
NS_ADDREF(buf);
*result = buf;
return NS_OK;
}
static NS_DEFINE_CID(kPageManagerCID, NS_PAGEMANAGER_CID);
NS_BASE nsresult
NS_NewPageBuffer(nsIBuffer* *result,
PRUint32 growBySize, PRUint32 maxSize)
{
nsresult rv;
NS_WITH_SERVICE(nsIAllocator, alloc, kPageManagerCID, &rv);
if (NS_FAILED(rv)) return rv;
nsBuffer* buf = new nsBuffer();
if (buf == nsnull)
return NS_ERROR_OUT_OF_MEMORY;
rv = buf->Init(growBySize, maxSize, alloc);
if (NS_FAILED(rv)) {
delete buf;
return rv;
}
NS_ADDREF(buf);
*result = buf;
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////