michael@0: /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ michael@0: /* vim:set ts=4 sts=4 sw=4 cin et: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* michael@0: * The storage stream provides an internal buffer that can be filled by a michael@0: * client using a single output stream. One or more independent input streams michael@0: * can be created to read the data out non-destructively. The implementation michael@0: * uses a segmented buffer internally to avoid realloc'ing of large buffers, michael@0: * with the attendant performance loss and heap fragmentation. michael@0: */ michael@0: michael@0: #include "nsAlgorithm.h" michael@0: #include "nsStorageStream.h" michael@0: #include "nsSegmentedBuffer.h" michael@0: #include "nsStreamUtils.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nsIInputStream.h" michael@0: #include "nsISeekableStream.h" michael@0: #include "prlog.h" michael@0: #include "mozilla/Attributes.h" michael@0: #include "mozilla/Likely.h" michael@0: #include "mozilla/MathAlgorithms.h" michael@0: michael@0: #if defined(PR_LOGGING) michael@0: // michael@0: // Log module for StorageStream logging... michael@0: // michael@0: // To enable logging (see prlog.h for full details): michael@0: // michael@0: // set NSPR_LOG_MODULES=StorageStreamLog:5 michael@0: // set NSPR_LOG_FILE=nspr.log michael@0: // michael@0: // this enables PR_LOG_DEBUG level information and places all output in michael@0: // the file nspr.log michael@0: // michael@0: static PRLogModuleInfo* michael@0: GetStorageStreamLog() michael@0: { michael@0: static PRLogModuleInfo *sLog; michael@0: if (!sLog) michael@0: sLog = PR_NewLogModule("nsStorageStream"); michael@0: return sLog; michael@0: } michael@0: #endif michael@0: #ifdef LOG michael@0: #undef LOG michael@0: #endif michael@0: #define LOG(args) PR_LOG(GetStorageStreamLog(), PR_LOG_DEBUG, args) michael@0: michael@0: nsStorageStream::nsStorageStream() michael@0: : mSegmentedBuffer(0), mSegmentSize(0), mWriteInProgress(false), michael@0: mLastSegmentNum(-1), mWriteCursor(0), mSegmentEnd(0), mLogicalLength(0) michael@0: { michael@0: LOG(("Creating nsStorageStream [%p].\n", this)); michael@0: } michael@0: michael@0: nsStorageStream::~nsStorageStream() michael@0: { michael@0: delete mSegmentedBuffer; michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(nsStorageStream, michael@0: nsIStorageStream, michael@0: nsIOutputStream) michael@0: michael@0: NS_IMETHODIMP michael@0: nsStorageStream::Init(uint32_t segmentSize, uint32_t maxSize) michael@0: { michael@0: mSegmentedBuffer = new nsSegmentedBuffer(); michael@0: if (!mSegmentedBuffer) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: mSegmentSize = segmentSize; michael@0: mSegmentSizeLog2 = mozilla::FloorLog2(segmentSize); michael@0: michael@0: // Segment size must be a power of two michael@0: if (mSegmentSize != ((uint32_t)1 << mSegmentSizeLog2)) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: return mSegmentedBuffer->Init(segmentSize, maxSize); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsStorageStream::GetOutputStream(int32_t aStartingOffset, michael@0: nsIOutputStream * *aOutputStream) michael@0: { michael@0: if (NS_WARN_IF(!aOutputStream)) michael@0: return NS_ERROR_INVALID_ARG; michael@0: if (NS_WARN_IF(!mSegmentedBuffer)) michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: if (mWriteInProgress) michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: nsresult rv = Seek(aStartingOffset); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: // Enlarge the last segment in the buffer so that it is the same size as michael@0: // all the other segments in the buffer. (It may have been realloc'ed michael@0: // smaller in the Close() method.) michael@0: if (mLastSegmentNum >= 0) michael@0: if (mSegmentedBuffer->ReallocLastSegment(mSegmentSize)) { michael@0: // Need to re-Seek, since realloc changed segment base pointer michael@0: rv = Seek(aStartingOffset); michael@0: if (NS_FAILED(rv)) return rv; michael@0: } michael@0: michael@0: NS_ADDREF(this); michael@0: *aOutputStream = static_cast(this); michael@0: mWriteInProgress = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsStorageStream::Close() michael@0: { michael@0: if (NS_WARN_IF(!mSegmentedBuffer)) michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: mWriteInProgress = false; michael@0: michael@0: int32_t segmentOffset = SegOffset(mLogicalLength); michael@0: michael@0: // Shrink the final segment in the segmented buffer to the minimum size michael@0: // needed to contain the data, so as to conserve memory. michael@0: if (segmentOffset) michael@0: mSegmentedBuffer->ReallocLastSegment(segmentOffset); michael@0: michael@0: mWriteCursor = 0; michael@0: mSegmentEnd = 0; michael@0: michael@0: LOG(("nsStorageStream [%p] Close mWriteCursor=%x mSegmentEnd=%x\n", michael@0: this, mWriteCursor, mSegmentEnd)); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsStorageStream::Flush() michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsStorageStream::Write(const char *aBuffer, uint32_t aCount, uint32_t *aNumWritten) michael@0: { michael@0: if (NS_WARN_IF(!aNumWritten) || NS_WARN_IF(!aBuffer)) michael@0: return NS_ERROR_INVALID_ARG; michael@0: if (NS_WARN_IF(!mSegmentedBuffer)) michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: const char* readCursor; michael@0: uint32_t count, availableInSegment, remaining; michael@0: nsresult rv = NS_OK; michael@0: michael@0: LOG(("nsStorageStream [%p] Write mWriteCursor=%x mSegmentEnd=%x aCount=%d\n", michael@0: this, mWriteCursor, mSegmentEnd, aCount)); michael@0: michael@0: remaining = aCount; michael@0: readCursor = aBuffer; michael@0: // If no segments have been created yet, create one even if we don't have michael@0: // to write any data; this enables creating an input stream which reads from michael@0: // the very end of the data for any amount of data in the stream (i.e. michael@0: // this stream contains N bytes of data and newInputStream(N) is called), michael@0: // even for N=0 (with the caveat that we require .write("", 0) be called to michael@0: // initialize internal buffers). michael@0: bool firstTime = mSegmentedBuffer->GetSegmentCount() == 0; michael@0: while (remaining || MOZ_UNLIKELY(firstTime)) { michael@0: firstTime = false; michael@0: availableInSegment = mSegmentEnd - mWriteCursor; michael@0: if (!availableInSegment) { michael@0: mWriteCursor = mSegmentedBuffer->AppendNewSegment(); michael@0: if (!mWriteCursor) { michael@0: mSegmentEnd = 0; michael@0: rv = NS_ERROR_OUT_OF_MEMORY; michael@0: goto out; michael@0: } michael@0: mLastSegmentNum++; michael@0: mSegmentEnd = mWriteCursor + mSegmentSize; michael@0: availableInSegment = mSegmentEnd - mWriteCursor; michael@0: LOG(("nsStorageStream [%p] Write (new seg) mWriteCursor=%x mSegmentEnd=%x\n", michael@0: this, mWriteCursor, mSegmentEnd)); michael@0: } michael@0: michael@0: count = XPCOM_MIN(availableInSegment, remaining); michael@0: memcpy(mWriteCursor, readCursor, count); michael@0: remaining -= count; michael@0: readCursor += count; michael@0: mWriteCursor += count; michael@0: LOG(("nsStorageStream [%p] Writing mWriteCursor=%x mSegmentEnd=%x count=%d\n", michael@0: this, mWriteCursor, mSegmentEnd, count)); michael@0: }; michael@0: michael@0: out: michael@0: *aNumWritten = aCount - remaining; michael@0: mLogicalLength += *aNumWritten; michael@0: michael@0: LOG(("nsStorageStream [%p] Wrote mWriteCursor=%x mSegmentEnd=%x numWritten=%d\n", michael@0: this, mWriteCursor, mSegmentEnd, *aNumWritten)); michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsStorageStream::WriteFrom(nsIInputStream *inStr, uint32_t count, uint32_t *_retval) michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsStorageStream::WriteSegments(nsReadSegmentFun reader, void * closure, uint32_t count, uint32_t *_retval) michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsStorageStream::IsNonBlocking(bool *aNonBlocking) michael@0: { michael@0: *aNonBlocking = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsStorageStream::GetLength(uint32_t *aLength) michael@0: { michael@0: *aLength = mLogicalLength; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Truncate the buffer by deleting the end segments michael@0: NS_IMETHODIMP michael@0: nsStorageStream::SetLength(uint32_t aLength) michael@0: { michael@0: if (NS_WARN_IF(!mSegmentedBuffer)) michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: if (mWriteInProgress) michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: if (aLength > mLogicalLength) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: int32_t newLastSegmentNum = SegNum(aLength); michael@0: int32_t segmentOffset = SegOffset(aLength); michael@0: if (segmentOffset == 0) michael@0: newLastSegmentNum--; michael@0: michael@0: while (newLastSegmentNum < mLastSegmentNum) { michael@0: mSegmentedBuffer->DeleteLastSegment(); michael@0: mLastSegmentNum--; michael@0: } michael@0: michael@0: mLogicalLength = aLength; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsStorageStream::GetWriteInProgress(bool *aWriteInProgress) michael@0: { michael@0: *aWriteInProgress = mWriteInProgress; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_METHOD michael@0: nsStorageStream::Seek(int32_t aPosition) michael@0: { michael@0: if (NS_WARN_IF(!mSegmentedBuffer)) michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: // An argument of -1 means "seek to end of stream" michael@0: if (aPosition == -1) michael@0: aPosition = mLogicalLength; michael@0: michael@0: // Seeking beyond the buffer end is illegal michael@0: if ((uint32_t)aPosition > mLogicalLength) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: // Seeking backwards in the write stream results in truncation michael@0: SetLength(aPosition); michael@0: michael@0: // Special handling for seek to start-of-buffer michael@0: if (aPosition == 0) { michael@0: mWriteCursor = 0; michael@0: mSegmentEnd = 0; michael@0: LOG(("nsStorageStream [%p] Seek mWriteCursor=%x mSegmentEnd=%x\n", michael@0: this, mWriteCursor, mSegmentEnd)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Segment may have changed, so reset pointers michael@0: mWriteCursor = mSegmentedBuffer->GetSegment(mLastSegmentNum); michael@0: NS_ASSERTION(mWriteCursor, "null mWriteCursor"); michael@0: mSegmentEnd = mWriteCursor + mSegmentSize; michael@0: michael@0: // Adjust write cursor for current segment offset. This test is necessary michael@0: // because SegNum may reference the next-to-be-allocated segment, in which michael@0: // case we need to be pointing at the end of the last segment. michael@0: int32_t segmentOffset = SegOffset(aPosition); michael@0: if (segmentOffset == 0 && (SegNum(aPosition) > (uint32_t) mLastSegmentNum)) michael@0: mWriteCursor = mSegmentEnd; michael@0: else michael@0: mWriteCursor += segmentOffset; michael@0: michael@0: LOG(("nsStorageStream [%p] Seek mWriteCursor=%x mSegmentEnd=%x\n", michael@0: this, mWriteCursor, mSegmentEnd)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: // There can be many nsStorageInputStreams for a single nsStorageStream michael@0: class nsStorageInputStream MOZ_FINAL : public nsIInputStream michael@0: , public nsISeekableStream michael@0: { michael@0: public: michael@0: nsStorageInputStream(nsStorageStream *aStorageStream, uint32_t aSegmentSize) michael@0: : mStorageStream(aStorageStream), mReadCursor(0), michael@0: mSegmentEnd(0), mSegmentNum(0), michael@0: mSegmentSize(aSegmentSize), mLogicalCursor(0), michael@0: mStatus(NS_OK) michael@0: { michael@0: NS_ADDREF(mStorageStream); michael@0: } michael@0: michael@0: NS_DECL_THREADSAFE_ISUPPORTS michael@0: NS_DECL_NSIINPUTSTREAM michael@0: NS_DECL_NSISEEKABLESTREAM michael@0: michael@0: private: michael@0: ~nsStorageInputStream() michael@0: { michael@0: NS_IF_RELEASE(mStorageStream); michael@0: } michael@0: michael@0: protected: michael@0: NS_METHOD Seek(uint32_t aPosition); michael@0: michael@0: friend class nsStorageStream; michael@0: michael@0: private: michael@0: nsStorageStream* mStorageStream; michael@0: uint32_t mReadCursor; // Next memory location to read byte, or 0 michael@0: uint32_t mSegmentEnd; // One byte past end of current buffer segment michael@0: uint32_t mSegmentNum; // Segment number containing read cursor michael@0: uint32_t mSegmentSize; // All segments, except the last, are of this size michael@0: uint32_t mLogicalCursor; // Logical offset into stream michael@0: nsresult mStatus; michael@0: michael@0: uint32_t SegNum(uint32_t aPosition) {return aPosition >> mStorageStream->mSegmentSizeLog2;} michael@0: uint32_t SegOffset(uint32_t aPosition) {return aPosition & (mSegmentSize - 1);} michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS(nsStorageInputStream, michael@0: nsIInputStream, michael@0: nsISeekableStream) michael@0: michael@0: NS_IMETHODIMP michael@0: nsStorageStream::NewInputStream(int32_t aStartingOffset, nsIInputStream* *aInputStream) michael@0: { michael@0: if (NS_WARN_IF(!mSegmentedBuffer)) michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: nsStorageInputStream *inputStream = new nsStorageInputStream(this, mSegmentSize); michael@0: if (!inputStream) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: NS_ADDREF(inputStream); michael@0: michael@0: nsresult rv = inputStream->Seek(aStartingOffset); michael@0: if (NS_FAILED(rv)) { michael@0: NS_RELEASE(inputStream); michael@0: return rv; michael@0: } michael@0: michael@0: *aInputStream = inputStream; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsStorageInputStream::Close() michael@0: { michael@0: mStatus = NS_BASE_STREAM_CLOSED; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsStorageInputStream::Available(uint64_t *aAvailable) michael@0: { michael@0: if (NS_FAILED(mStatus)) michael@0: return mStatus; michael@0: michael@0: *aAvailable = mStorageStream->mLogicalLength - mLogicalCursor; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsStorageInputStream::Read(char* aBuffer, uint32_t aCount, uint32_t *aNumRead) michael@0: { michael@0: return ReadSegments(NS_CopySegmentToBuffer, aBuffer, aCount, aNumRead); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsStorageInputStream::ReadSegments(nsWriteSegmentFun writer, void * closure, uint32_t aCount, uint32_t *aNumRead) michael@0: { michael@0: *aNumRead = 0; michael@0: if (mStatus == NS_BASE_STREAM_CLOSED) michael@0: return NS_OK; michael@0: if (NS_FAILED(mStatus)) michael@0: return mStatus; michael@0: michael@0: uint32_t count, availableInSegment, remainingCapacity, bytesConsumed; michael@0: nsresult rv; michael@0: michael@0: remainingCapacity = aCount; michael@0: while (remainingCapacity) { michael@0: availableInSegment = mSegmentEnd - mReadCursor; michael@0: if (!availableInSegment) { michael@0: uint32_t available = mStorageStream->mLogicalLength - mLogicalCursor; michael@0: if (!available) michael@0: goto out; michael@0: michael@0: mSegmentNum++; michael@0: mReadCursor = 0; michael@0: mSegmentEnd = XPCOM_MIN(mSegmentSize, available); michael@0: availableInSegment = mSegmentEnd; michael@0: } michael@0: const char *cur = mStorageStream->mSegmentedBuffer->GetSegment(mSegmentNum); michael@0: michael@0: count = XPCOM_MIN(availableInSegment, remainingCapacity); michael@0: rv = writer(this, closure, cur + mReadCursor, aCount - remainingCapacity, michael@0: count, &bytesConsumed); michael@0: if (NS_FAILED(rv) || (bytesConsumed == 0)) michael@0: break; michael@0: remainingCapacity -= bytesConsumed; michael@0: mReadCursor += bytesConsumed; michael@0: mLogicalCursor += bytesConsumed; michael@0: }; michael@0: michael@0: out: michael@0: *aNumRead = aCount - remainingCapacity; michael@0: michael@0: bool isWriteInProgress = false; michael@0: if (NS_FAILED(mStorageStream->GetWriteInProgress(&isWriteInProgress))) michael@0: isWriteInProgress = false; michael@0: michael@0: if (*aNumRead == 0 && isWriteInProgress) michael@0: return NS_BASE_STREAM_WOULD_BLOCK; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsStorageInputStream::IsNonBlocking(bool *aNonBlocking) michael@0: { michael@0: // TODO: This class should implement nsIAsyncInputStream so that callers michael@0: // have some way of dealing with NS_BASE_STREAM_WOULD_BLOCK errors. michael@0: michael@0: *aNonBlocking = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsStorageInputStream::Seek(int32_t aWhence, int64_t aOffset) michael@0: { michael@0: if (NS_FAILED(mStatus)) michael@0: return mStatus; michael@0: michael@0: int64_t pos = aOffset; michael@0: michael@0: switch (aWhence) { michael@0: case NS_SEEK_SET: michael@0: break; michael@0: case NS_SEEK_CUR: michael@0: pos += mLogicalCursor; michael@0: break; michael@0: case NS_SEEK_END: michael@0: pos += mStorageStream->mLogicalLength; michael@0: break; michael@0: default: michael@0: NS_NOTREACHED("unexpected whence value"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: if (pos == int64_t(mLogicalCursor)) michael@0: return NS_OK; michael@0: michael@0: return Seek(pos); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsStorageInputStream::Tell(int64_t *aResult) michael@0: { michael@0: if (NS_FAILED(mStatus)) michael@0: return mStatus; michael@0: michael@0: *aResult = mLogicalCursor; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsStorageInputStream::SetEOF() michael@0: { michael@0: NS_NOTREACHED("nsStorageInputStream::SetEOF"); michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: NS_METHOD michael@0: nsStorageInputStream::Seek(uint32_t aPosition) michael@0: { michael@0: uint32_t length = mStorageStream->mLogicalLength; michael@0: if (aPosition > length) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: if (length == 0) michael@0: return NS_OK; michael@0: michael@0: mSegmentNum = SegNum(aPosition); michael@0: mReadCursor = SegOffset(aPosition); michael@0: uint32_t available = length - aPosition; michael@0: mSegmentEnd = mReadCursor + XPCOM_MIN(mSegmentSize - mReadCursor, available); michael@0: mLogicalCursor = aPosition; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: NS_NewStorageStream(uint32_t segmentSize, uint32_t maxSize, nsIStorageStream **result) michael@0: { michael@0: nsStorageStream* storageStream = new nsStorageStream(); michael@0: if (!storageStream) return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: NS_ADDREF(storageStream); michael@0: nsresult rv = storageStream->Init(segmentSize, maxSize); michael@0: if (NS_FAILED(rv)) { michael@0: NS_RELEASE(storageStream); michael@0: return rv; michael@0: } michael@0: *result = storageStream; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Undefine LOG, so that other .cpp files (or their includes) won't complain michael@0: // about it already being defined, when we build in unified mode. michael@0: #undef LOG