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: #include "CacheLog.h" michael@0: #include "CacheFileOutputStream.h" michael@0: michael@0: #include "CacheFile.h" michael@0: #include "CacheEntry.h" michael@0: #include "nsStreamUtils.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "mozilla/DebugOnly.h" michael@0: #include michael@0: michael@0: namespace mozilla { michael@0: namespace net { michael@0: michael@0: NS_IMPL_ADDREF(CacheFileOutputStream) michael@0: NS_IMETHODIMP_(MozExternalRefCountType) michael@0: CacheFileOutputStream::Release() michael@0: { michael@0: NS_PRECONDITION(0 != mRefCnt, "dup release"); michael@0: nsrefcnt count = --mRefCnt; michael@0: NS_LOG_RELEASE(this, count, "CacheFileOutputStream"); michael@0: michael@0: if (0 == count) { michael@0: mRefCnt = 1; michael@0: { michael@0: CacheFileAutoLock lock(mFile); michael@0: mFile->RemoveOutput(this); michael@0: } michael@0: delete (this); michael@0: return 0; michael@0: } michael@0: michael@0: return count; michael@0: } michael@0: michael@0: NS_INTERFACE_MAP_BEGIN(CacheFileOutputStream) michael@0: NS_INTERFACE_MAP_ENTRY(nsIOutputStream) michael@0: NS_INTERFACE_MAP_ENTRY(nsIAsyncOutputStream) michael@0: NS_INTERFACE_MAP_ENTRY(nsISeekableStream) michael@0: NS_INTERFACE_MAP_ENTRY(mozilla::net::CacheFileChunkListener) michael@0: NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIOutputStream) michael@0: NS_INTERFACE_MAP_END_THREADSAFE michael@0: michael@0: CacheFileOutputStream::CacheFileOutputStream(CacheFile *aFile, michael@0: CacheOutputCloseListener *aCloseListener) michael@0: : mFile(aFile) michael@0: , mCloseListener(aCloseListener) michael@0: , mPos(0) michael@0: , mClosed(false) michael@0: , mStatus(NS_OK) michael@0: , mCallbackFlags(0) michael@0: { michael@0: LOG(("CacheFileOutputStream::CacheFileOutputStream() [this=%p]", this)); michael@0: MOZ_COUNT_CTOR(CacheFileOutputStream); michael@0: } michael@0: michael@0: CacheFileOutputStream::~CacheFileOutputStream() michael@0: { michael@0: LOG(("CacheFileOutputStream::~CacheFileOutputStream() [this=%p]", this)); michael@0: MOZ_COUNT_DTOR(CacheFileOutputStream); michael@0: } michael@0: michael@0: // nsIOutputStream michael@0: NS_IMETHODIMP michael@0: CacheFileOutputStream::Close() michael@0: { michael@0: LOG(("CacheFileOutputStream::Close() [this=%p]", this)); michael@0: return CloseWithStatus(NS_OK); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: CacheFileOutputStream::Flush() michael@0: { michael@0: // TODO do we need to implement flush ??? michael@0: LOG(("CacheFileOutputStream::Flush() [this=%p]", this)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: CacheFileOutputStream::Write(const char * aBuf, uint32_t aCount, michael@0: uint32_t *_retval) michael@0: { michael@0: CacheFileAutoLock lock(mFile); michael@0: michael@0: LOG(("CacheFileOutputStream::Write() [this=%p, count=%d]", this, aCount)); michael@0: michael@0: if (mClosed) { michael@0: LOG(("CacheFileOutputStream::Write() - Stream is closed. [this=%p, " michael@0: "status=0x%08x]", this, mStatus)); michael@0: michael@0: return NS_FAILED(mStatus) ? mStatus : NS_BASE_STREAM_CLOSED; michael@0: } michael@0: michael@0: *_retval = aCount; michael@0: michael@0: while (aCount) { michael@0: EnsureCorrectChunk(false); michael@0: if (NS_FAILED(mStatus)) michael@0: return mStatus; michael@0: michael@0: FillHole(); michael@0: michael@0: uint32_t chunkOffset = mPos - (mPos / kChunkSize) * kChunkSize; michael@0: uint32_t canWrite = kChunkSize - chunkOffset; michael@0: uint32_t thisWrite = std::min(static_cast(canWrite), aCount); michael@0: mChunk->EnsureBufSize(chunkOffset + thisWrite); michael@0: memcpy(mChunk->BufForWriting() + chunkOffset, aBuf, thisWrite); michael@0: michael@0: mPos += thisWrite; michael@0: aBuf += thisWrite; michael@0: aCount -= thisWrite; michael@0: michael@0: mChunk->UpdateDataSize(chunkOffset, thisWrite, false); michael@0: } michael@0: michael@0: EnsureCorrectChunk(true); michael@0: michael@0: LOG(("CacheFileOutputStream::Write() - Wrote %d bytes [this=%p]", michael@0: *_retval, this)); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: CacheFileOutputStream::WriteFrom(nsIInputStream *aFromStream, uint32_t aCount, michael@0: uint32_t *_retval) michael@0: { michael@0: LOG(("CacheFileOutputStream::WriteFrom() - NOT_IMPLEMENTED [this=%p, from=%p" michael@0: ", count=%d]", this, aFromStream, aCount)); michael@0: michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: CacheFileOutputStream::WriteSegments(nsReadSegmentFun aReader, void *aClosure, michael@0: uint32_t aCount, uint32_t *_retval) michael@0: { michael@0: LOG(("CacheFileOutputStream::WriteSegments() - NOT_IMPLEMENTED [this=%p, " michael@0: "count=%d]", this, aCount)); michael@0: michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: CacheFileOutputStream::IsNonBlocking(bool *_retval) michael@0: { michael@0: *_retval = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // nsIAsyncOutputStream michael@0: NS_IMETHODIMP michael@0: CacheFileOutputStream::CloseWithStatus(nsresult aStatus) michael@0: { michael@0: CacheFileAutoLock lock(mFile); michael@0: michael@0: LOG(("CacheFileOutputStream::CloseWithStatus() [this=%p, aStatus=0x%08x]", michael@0: this, aStatus)); michael@0: michael@0: if (mClosed) { michael@0: MOZ_ASSERT(!mCallback); michael@0: return NS_OK; michael@0: } michael@0: michael@0: mClosed = true; michael@0: mStatus = NS_FAILED(aStatus) ? aStatus : NS_BASE_STREAM_CLOSED; michael@0: michael@0: if (mChunk) michael@0: ReleaseChunk(); michael@0: michael@0: if (mCallback) michael@0: NotifyListener(); michael@0: michael@0: mFile->RemoveOutput(this); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: CacheFileOutputStream::AsyncWait(nsIOutputStreamCallback *aCallback, michael@0: uint32_t aFlags, michael@0: uint32_t aRequestedCount, michael@0: nsIEventTarget *aEventTarget) michael@0: { michael@0: CacheFileAutoLock lock(mFile); michael@0: michael@0: LOG(("CacheFileOutputStream::AsyncWait() [this=%p, callback=%p, flags=%d, " michael@0: "requestedCount=%d, eventTarget=%p]", this, aCallback, aFlags, michael@0: aRequestedCount, aEventTarget)); michael@0: michael@0: mCallback = aCallback; michael@0: mCallbackFlags = aFlags; michael@0: michael@0: if (!mCallback) michael@0: return NS_OK; michael@0: michael@0: // The stream is blocking so it is writable at any time michael@0: if (mClosed || !(aFlags & WAIT_CLOSURE_ONLY)) michael@0: NotifyListener(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // nsISeekableStream michael@0: NS_IMETHODIMP michael@0: CacheFileOutputStream::Seek(int32_t whence, int64_t offset) michael@0: { michael@0: CacheFileAutoLock lock(mFile); michael@0: michael@0: LOG(("CacheFileOutputStream::Seek() [this=%p, whence=%d, offset=%lld]", michael@0: this, whence, offset)); michael@0: michael@0: if (mClosed) { michael@0: LOG(("CacheFileOutputStream::Seek() - Stream is closed. [this=%p]", this)); michael@0: return NS_BASE_STREAM_CLOSED; michael@0: } michael@0: michael@0: int64_t newPos = offset; michael@0: switch (whence) { michael@0: case NS_SEEK_SET: michael@0: break; michael@0: case NS_SEEK_CUR: michael@0: newPos += mPos; michael@0: break; michael@0: case NS_SEEK_END: michael@0: newPos += mFile->mDataSize; michael@0: break; michael@0: default: michael@0: NS_ERROR("invalid whence"); michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: mPos = newPos; michael@0: EnsureCorrectChunk(true); michael@0: michael@0: LOG(("CacheFileOutputStream::Seek() [this=%p, pos=%lld]", this, mPos)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: CacheFileOutputStream::Tell(int64_t *_retval) michael@0: { michael@0: CacheFileAutoLock lock(mFile); michael@0: michael@0: if (mClosed) { michael@0: LOG(("CacheFileOutputStream::Tell() - Stream is closed. [this=%p]", this)); michael@0: return NS_BASE_STREAM_CLOSED; michael@0: } michael@0: michael@0: *_retval = mPos; michael@0: michael@0: LOG(("CacheFileOutputStream::Tell() [this=%p, retval=%lld]", this, *_retval)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: CacheFileOutputStream::SetEOF() michael@0: { michael@0: MOZ_ASSERT(false, "CacheFileOutputStream::SetEOF() not implemented"); michael@0: // Right now we don't use SetEOF(). If we ever need this method, we need michael@0: // to think about what to do with input streams that already points beyond michael@0: // new EOF. michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: // CacheFileChunkListener michael@0: nsresult michael@0: CacheFileOutputStream::OnChunkRead(nsresult aResult, CacheFileChunk *aChunk) michael@0: { michael@0: MOZ_CRASH("CacheFileOutputStream::OnChunkRead should not be called!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFileOutputStream::OnChunkWritten(nsresult aResult, CacheFileChunk *aChunk) michael@0: { michael@0: MOZ_CRASH( michael@0: "CacheFileOutputStream::OnChunkWritten should not be called!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFileOutputStream::OnChunkAvailable(nsresult aResult, michael@0: uint32_t aChunkIdx, michael@0: CacheFileChunk *aChunk) michael@0: { michael@0: MOZ_CRASH( michael@0: "CacheFileOutputStream::OnChunkAvailable should not be called!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFileOutputStream::OnChunkUpdated(CacheFileChunk *aChunk) michael@0: { michael@0: MOZ_CRASH( michael@0: "CacheFileOutputStream::OnChunkUpdated should not be called!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: void CacheFileOutputStream::NotifyCloseListener() michael@0: { michael@0: nsRefPtr listener; michael@0: listener.swap(mCloseListener); michael@0: if (!listener) michael@0: return; michael@0: michael@0: listener->OnOutputClosed(); michael@0: } michael@0: michael@0: void michael@0: CacheFileOutputStream::ReleaseChunk() michael@0: { michael@0: LOG(("CacheFileOutputStream::ReleaseChunk() [this=%p, idx=%d]", michael@0: this, mChunk->Index())); michael@0: michael@0: mFile->ReleaseOutsideLock(mChunk.forget().take()); michael@0: } michael@0: michael@0: void michael@0: CacheFileOutputStream::EnsureCorrectChunk(bool aReleaseOnly) michael@0: { michael@0: mFile->AssertOwnsLock(); michael@0: michael@0: LOG(("CacheFileOutputStream::EnsureCorrectChunk() [this=%p, releaseOnly=%d]", michael@0: this, aReleaseOnly)); michael@0: michael@0: uint32_t chunkIdx = mPos / kChunkSize; michael@0: michael@0: if (mChunk) { michael@0: if (mChunk->Index() == chunkIdx) { michael@0: // we have a correct chunk michael@0: LOG(("CacheFileOutputStream::EnsureCorrectChunk() - Have correct chunk " michael@0: "[this=%p, idx=%d]", this, chunkIdx)); michael@0: michael@0: return; michael@0: } michael@0: else { michael@0: ReleaseChunk(); michael@0: } michael@0: } michael@0: michael@0: if (aReleaseOnly) michael@0: return; michael@0: michael@0: nsresult rv; michael@0: rv = mFile->GetChunkLocked(chunkIdx, true, nullptr, getter_AddRefs(mChunk)); michael@0: if (NS_FAILED(rv)) { michael@0: LOG(("CacheFileOutputStream::EnsureCorrectChunk() - GetChunkLocked failed. " michael@0: "[this=%p, idx=%d, rv=0x%08x]", this, chunkIdx, rv)); michael@0: mStatus = rv; michael@0: } michael@0: } michael@0: michael@0: void michael@0: CacheFileOutputStream::FillHole() michael@0: { michael@0: mFile->AssertOwnsLock(); michael@0: michael@0: MOZ_ASSERT(mChunk); michael@0: MOZ_ASSERT(mPos / kChunkSize == mChunk->Index()); michael@0: michael@0: uint32_t pos = mPos - (mPos / kChunkSize) * kChunkSize; michael@0: if (mChunk->DataSize() >= pos) michael@0: return; michael@0: michael@0: LOG(("CacheFileOutputStream::FillHole() - Zeroing hole in chunk %d, range " michael@0: "%d-%d [this=%p]", mChunk->Index(), mChunk->DataSize(), pos - 1, this)); michael@0: michael@0: mChunk->EnsureBufSize(pos); michael@0: memset(mChunk->BufForWriting() + mChunk->DataSize(), 0, michael@0: pos - mChunk->DataSize()); michael@0: michael@0: mChunk->UpdateDataSize(mChunk->DataSize(), pos - mChunk->DataSize(), false); michael@0: } michael@0: michael@0: void michael@0: CacheFileOutputStream::NotifyListener() michael@0: { michael@0: mFile->AssertOwnsLock(); michael@0: michael@0: LOG(("CacheFileOutputStream::NotifyListener() [this=%p]", this)); michael@0: michael@0: MOZ_ASSERT(mCallback); michael@0: michael@0: if (!mCallbackTarget) michael@0: mCallbackTarget = NS_GetCurrentThread(); michael@0: michael@0: nsCOMPtr asyncCallback = michael@0: NS_NewOutputStreamReadyEvent(mCallback, mCallbackTarget); michael@0: michael@0: mCallback = nullptr; michael@0: mCallbackTarget = nullptr; michael@0: michael@0: asyncCallback->OnOutputStreamReady(this); michael@0: } michael@0: michael@0: // Memory reporting michael@0: michael@0: size_t michael@0: CacheFileOutputStream::SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const michael@0: { michael@0: // Everything the stream keeps a reference to is already reported somewhere else. michael@0: // mFile reports itself. michael@0: // mChunk reported as part of CacheFile. michael@0: // mCloseListener is CacheEntry, already reported. michael@0: // mCallback is usually CacheFile or a class that is reported elsewhere. michael@0: return mallocSizeOf(this); michael@0: } michael@0: michael@0: } // net michael@0: } // mozilla