Source code

Revision control

Other Tools

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/SnappyCompressOutputStream.h"
#include <algorithm>
#include "nsStreamUtils.h"
#include "snappy/snappy.h"
namespace mozilla {
NS_IMPL_ISUPPORTS(SnappyCompressOutputStream, nsIOutputStream);
// static
const size_t SnappyCompressOutputStream::kMaxBlockSize = snappy::kBlockSize;
SnappyCompressOutputStream::SnappyCompressOutputStream(
nsIOutputStream* aBaseStream, size_t aBlockSize)
: mBaseStream(aBaseStream),
mBlockSize(std::min(aBlockSize, kMaxBlockSize)),
mNextByte(0),
mCompressedBufferLength(0),
mStreamIdentifierWritten(false) {
MOZ_ASSERT(mBlockSize > 0);
// This implementation only supports sync base streams. Verify this in debug
// builds. Note, this can be simpler than the check in
// SnappyUncompressInputStream because we don't have to deal with the
// nsStringInputStream oddness of being non-blocking and sync.
#ifdef DEBUG
bool baseNonBlocking;
nsresult rv = mBaseStream->IsNonBlocking(&baseNonBlocking);
MOZ_ASSERT(NS_SUCCEEDED(rv));
MOZ_ASSERT(!baseNonBlocking);
#endif
}
size_t SnappyCompressOutputStream::BlockSize() const { return mBlockSize; }
NS_IMETHODIMP
SnappyCompressOutputStream::Close() {
if (!mBaseStream) {
return NS_OK;
}
nsresult rv = Flush();
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
mBaseStream->Close();
mBaseStream = nullptr;
mBuffer = nullptr;
mCompressedBuffer = nullptr;
return NS_OK;
}
NS_IMETHODIMP
SnappyCompressOutputStream::Flush() {
if (!mBaseStream) {
return NS_BASE_STREAM_CLOSED;
}
nsresult rv = FlushToBaseStream();
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
mBaseStream->Flush();
return NS_OK;
}
NS_IMETHODIMP
SnappyCompressOutputStream::Write(const char* aBuf, uint32_t aCount,
uint32_t* aResultOut) {
return WriteSegments(NS_CopySegmentToBuffer, const_cast<char*>(aBuf), aCount,
aResultOut);
}
NS_IMETHODIMP
SnappyCompressOutputStream::WriteFrom(nsIInputStream*, uint32_t, uint32_t*) {
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
SnappyCompressOutputStream::WriteSegments(nsReadSegmentFun aReader,
void* aClosure, uint32_t aCount,
uint32_t* aBytesWrittenOut) {
*aBytesWrittenOut = 0;
if (!mBaseStream) {
return NS_BASE_STREAM_CLOSED;
}
if (!mBuffer) {
mBuffer.reset(new (fallible) char[mBlockSize]);
if (NS_WARN_IF(!mBuffer)) {
return NS_ERROR_OUT_OF_MEMORY;
}
}
while (aCount > 0) {
// Determine how much space is left in our flat, uncompressed buffer.
MOZ_ASSERT(mNextByte <= mBlockSize);
uint32_t remaining = mBlockSize - mNextByte;
// If it is full, then compress and flush the data to the base stream.
if (remaining == 0) {
nsresult rv = FlushToBaseStream();
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
// Now the entire buffer should be available for copying.
MOZ_ASSERT(!mNextByte);
remaining = mBlockSize;
}
uint32_t numToRead = std::min(remaining, aCount);
uint32_t numRead = 0;
nsresult rv = aReader(this, aClosure, &mBuffer[mNextByte],
*aBytesWrittenOut, numToRead, &numRead);
// As defined in nsIOutputStream.idl, do not pass reader func errors.
if (NS_FAILED(rv)) {
return NS_OK;
}
// End-of-file
if (numRead == 0) {
return NS_OK;
}
mNextByte += numRead;
*aBytesWrittenOut += numRead;
aCount -= numRead;
}
return NS_OK;
}
NS_IMETHODIMP
SnappyCompressOutputStream::IsNonBlocking(bool* aNonBlockingOut) {
*aNonBlockingOut = false;
return NS_OK;
}
SnappyCompressOutputStream::~SnappyCompressOutputStream() { Close(); }
nsresult SnappyCompressOutputStream::FlushToBaseStream() {
MOZ_ASSERT(mBaseStream);
// Lazily create the compressed buffer on our first flush. This
// allows us to report OOM during stream operation. This buffer
// will then get re-used until the stream is closed.
if (!mCompressedBuffer) {
mCompressedBufferLength = MaxCompressedBufferLength(mBlockSize);
mCompressedBuffer.reset(new (fallible) char[mCompressedBufferLength]);
if (NS_WARN_IF(!mCompressedBuffer)) {
return NS_ERROR_OUT_OF_MEMORY;
}
}
// The first chunk must be a StreamIdentifier chunk. Write it out
// if we have not done so already.
nsresult rv = MaybeFlushStreamIdentifier();
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
// Compress the data to our internal compressed buffer.
size_t compressedLength;
rv = WriteCompressedData(mCompressedBuffer.get(), mCompressedBufferLength,
mBuffer.get(), mNextByte, &compressedLength);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
MOZ_ASSERT(compressedLength > 0);
mNextByte = 0;
// Write the compressed buffer out to the base stream.
uint32_t numWritten = 0;
rv = WriteAll(mCompressedBuffer.get(), compressedLength, &numWritten);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
MOZ_ASSERT(compressedLength == numWritten);
return NS_OK;
}
nsresult SnappyCompressOutputStream::MaybeFlushStreamIdentifier() {
MOZ_ASSERT(mCompressedBuffer);
if (mStreamIdentifierWritten) {
return NS_OK;
}
// Build the StreamIdentifier in our compressed buffer.
size_t compressedLength;
nsresult rv = WriteStreamIdentifier(
mCompressedBuffer.get(), mCompressedBufferLength, &compressedLength);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
// Write the compressed buffer out to the base stream.
uint32_t numWritten = 0;
rv = WriteAll(mCompressedBuffer.get(), compressedLength, &numWritten);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
MOZ_ASSERT(compressedLength == numWritten);
mStreamIdentifierWritten = true;
return NS_OK;
}
nsresult SnappyCompressOutputStream::WriteAll(const char* aBuf, uint32_t aCount,
uint32_t* aBytesWrittenOut) {
*aBytesWrittenOut = 0;
if (!mBaseStream) {
return NS_BASE_STREAM_CLOSED;
}
uint32_t offset = 0;
while (aCount > 0) {
uint32_t numWritten = 0;
nsresult rv = mBaseStream->Write(aBuf + offset, aCount, &numWritten);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
offset += numWritten;
aCount -= numWritten;
*aBytesWrittenOut += numWritten;
}
return NS_OK;
}
} // namespace mozilla