1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "CacheFileInputStream.h"
9 #include "nsStreamUtils.h"
10 #include "nsThreadUtils.h"
13 namespace mozilla::net
{
15 NS_IMPL_ADDREF(CacheFileInputStream
)
16 NS_IMETHODIMP_(MozExternalRefCountType
)
17 CacheFileInputStream::Release() {
18 MOZ_ASSERT(0 != mRefCnt
, "dup release");
19 nsrefcnt count
= --mRefCnt
;
20 NS_LOG_RELEASE(this, count
, "CacheFileInputStream");
29 CacheFileAutoLock
lock(mFile
);
30 mFile
->RemoveInput(this, mStatus
);
36 NS_INTERFACE_MAP_BEGIN(CacheFileInputStream
)
37 NS_INTERFACE_MAP_ENTRY(nsIInputStream
)
38 NS_INTERFACE_MAP_ENTRY(nsIAsyncInputStream
)
39 NS_INTERFACE_MAP_ENTRY(nsISeekableStream
)
40 NS_INTERFACE_MAP_ENTRY(nsITellableStream
)
41 NS_INTERFACE_MAP_ENTRY(mozilla::net::CacheFileChunkListener
)
42 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports
, nsIInputStream
)
45 CacheFileInputStream::CacheFileInputStream(CacheFile
* aFile
,
47 bool aAlternativeData
)
52 mInReadSegments(false),
53 mWaitingForUpdate(false),
54 mAlternativeData(aAlternativeData
),
55 mListeningForChunk(-1),
57 mCacheEntryHandle(aEntry
) {
58 LOG(("CacheFileInputStream::CacheFileInputStream() [this=%p]", this));
60 if (mAlternativeData
) {
61 mPos
= mFile
->mAltDataOffset
;
65 CacheFileInputStream::~CacheFileInputStream() {
66 LOG(("CacheFileInputStream::~CacheFileInputStream() [this=%p]", this));
67 MOZ_ASSERT(!mInReadSegments
);
72 CacheFileInputStream::Close() {
73 LOG(("CacheFileInputStream::Close() [this=%p]", this));
74 return CloseWithStatus(NS_OK
);
78 CacheFileInputStream::Available(uint64_t* _retval
) {
79 CacheFileAutoLock
lock(mFile
);
83 ("CacheFileInputStream::Available() - Stream is closed. [this=%p, "
84 "status=0x%08" PRIx32
"]",
85 this, static_cast<uint32_t>(mStatus
)));
86 return NS_FAILED(mStatus
) ? mStatus
: NS_BASE_STREAM_CLOSED
;
89 EnsureCorrectChunk(false);
90 if (NS_FAILED(mStatus
)) {
92 ("CacheFileInputStream::Available() - EnsureCorrectChunk failed. "
93 "[this=%p, status=0x%08" PRIx32
"]",
94 this, static_cast<uint32_t>(mStatus
)));
102 int64_t canRead
= mFile
->BytesFromChunk(mChunk
->Index(), mAlternativeData
);
103 canRead
-= (mPos
% kChunkSize
);
107 } else if (canRead
== 0 && !mFile
->OutputStreamExists(mAlternativeData
)) {
108 rv
= NS_BASE_STREAM_CLOSED
;
112 LOG(("CacheFileInputStream::Available() [this=%p, retval=%" PRIu64
113 ", rv=0x%08" PRIx32
"]",
114 this, *_retval
, static_cast<uint32_t>(rv
)));
120 CacheFileInputStream::Read(char* aBuf
, uint32_t aCount
, uint32_t* _retval
) {
121 LOG(("CacheFileInputStream::Read() [this=%p, count=%d]", this, aCount
));
122 return ReadSegments(NS_CopySegmentToBuffer
, aBuf
, aCount
, _retval
);
126 CacheFileInputStream::ReadSegments(nsWriteSegmentFun aWriter
, void* aClosure
,
127 uint32_t aCount
, uint32_t* _retval
) {
128 CacheFileAutoLock
lock(mFile
);
130 LOG(("CacheFileInputStream::ReadSegments() [this=%p, count=%d]", this,
137 if (mInReadSegments
) {
139 ("CacheFileInputStream::ReadSegments() - Cannot be called while the "
140 "stream is in ReadSegments!"));
141 return NS_ERROR_UNEXPECTED
;
146 ("CacheFileInputStream::ReadSegments() - Stream is closed. [this=%p, "
147 "status=0x%08" PRIx32
"]",
148 this, static_cast<uint32_t>(mStatus
)));
150 if (NS_FAILED(mStatus
)) {
161 EnsureCorrectChunk(false);
164 if (NS_FAILED(mStatus
)) return mStatus
;
167 if (mListeningForChunk
== -1) {
170 return NS_BASE_STREAM_WOULD_BLOCK
;
173 CacheFileChunkReadHandle hnd
= mChunk
->GetReadHandle();
174 int64_t canRead
= CanRead(&hnd
);
175 if (NS_FAILED(mStatus
)) {
180 // file was truncated ???
181 MOZ_ASSERT(false, "SetEOF is currenty not implemented?!");
183 } else if (canRead
> 0) {
184 uint32_t toRead
= std::min(static_cast<uint32_t>(canRead
), aCount
);
186 const char* buf
= hnd
.Buf() + (mPos
- hnd
.Offset());
188 mInReadSegments
= true;
191 rv
= aWriter(this, aClosure
, buf
, *_retval
, toRead
, &read
);
194 mInReadSegments
= false;
196 if (NS_SUCCEEDED(rv
)) {
197 MOZ_ASSERT(read
<= toRead
,
198 "writer should not write more than we asked it to write");
205 // The last chunk is released after the caller closes this stream.
206 EnsureCorrectChunk(false);
208 if (mChunk
&& aCount
) {
209 // Check whether there is more data available to read.
216 // The stream was closed from aWriter, do the cleanup.
222 if (*_retval
== 0 && mFile
->OutputStreamExists(mAlternativeData
)) {
223 rv
= NS_BASE_STREAM_WOULD_BLOCK
;
232 LOG(("CacheFileInputStream::ReadSegments() [this=%p, rv=0x%08" PRIx32
234 this, static_cast<uint32_t>(rv
), *_retval
));
240 CacheFileInputStream::IsNonBlocking(bool* _retval
) {
245 // nsIAsyncInputStream
247 CacheFileInputStream::CloseWithStatus(nsresult aStatus
) {
248 CacheFileAutoLock
lock(mFile
);
250 LOG(("CacheFileInputStream::CloseWithStatus() [this=%p, aStatus=0x%08" PRIx32
252 this, static_cast<uint32_t>(aStatus
)));
254 CloseWithStatusLocked(aStatus
);
258 void CacheFileInputStream::CloseWithStatusLocked(nsresult aStatus
) {
260 ("CacheFileInputStream::CloseWithStatusLocked() [this=%p, "
261 "aStatus=0x%08" PRIx32
"]",
262 this, static_cast<uint32_t>(aStatus
)));
265 // We notify listener and null out mCallback immediately after closing
266 // the stream. If we're in ReadSegments we postpone notification until we
267 // step out from ReadSegments. So if the stream is already closed the
268 // following assertion must be true.
269 MOZ_ASSERT(!mCallback
|| mInReadSegments
);
274 mStatus
= NS_FAILED(aStatus
) ? aStatus
: NS_BASE_STREAM_CLOSED
;
276 if (!mInReadSegments
) {
281 void CacheFileInputStream::CleanUp() {
282 MOZ_ASSERT(!mInReadSegments
);
289 // TODO propagate error from input stream to other streams ???
291 MaybeNotifyListener();
293 mFile
->ReleaseOutsideLock(std::move(mCacheEntryHandle
));
297 CacheFileInputStream::AsyncWait(nsIInputStreamCallback
* aCallback
,
298 uint32_t aFlags
, uint32_t aRequestedCount
,
299 nsIEventTarget
* aEventTarget
) {
300 CacheFileAutoLock
lock(mFile
);
303 ("CacheFileInputStream::AsyncWait() [this=%p, callback=%p, flags=%d, "
304 "requestedCount=%d, eventTarget=%p]",
305 this, aCallback
, aFlags
, aRequestedCount
, aEventTarget
));
307 if (mInReadSegments
) {
309 ("CacheFileInputStream::AsyncWait() - Cannot be called while the stream"
310 " is in ReadSegments!"));
312 "Unexpected call. If it's a valid usage implement it. "
313 "Otherwise fix the caller.");
314 return NS_ERROR_UNEXPECTED
;
317 mCallback
= aCallback
;
318 mCallbackFlags
= aFlags
;
319 mCallbackTarget
= aEventTarget
;
322 if (mWaitingForUpdate
) {
323 mChunk
->CancelWait(this);
324 mWaitingForUpdate
= false;
334 EnsureCorrectChunk(false);
336 MaybeNotifyListener();
343 CacheFileInputStream::Seek(int32_t whence
, int64_t offset
) {
344 CacheFileAutoLock
lock(mFile
);
346 LOG(("CacheFileInputStream::Seek() [this=%p, whence=%d, offset=%" PRId64
"]",
347 this, whence
, offset
));
349 if (mInReadSegments
) {
351 ("CacheFileInputStream::Seek() - Cannot be called while the stream is "
352 "in ReadSegments!"));
353 return NS_ERROR_UNEXPECTED
;
357 LOG(("CacheFileInputStream::Seek() - Stream is closed. [this=%p]", this));
358 return NS_BASE_STREAM_CLOSED
;
361 int64_t newPos
= offset
;
364 if (mAlternativeData
) {
365 newPos
+= mFile
->mAltDataOffset
;
372 if (mAlternativeData
) {
373 newPos
+= mFile
->mDataSize
;
375 newPos
+= mFile
->mAltDataOffset
;
379 NS_ERROR("invalid whence");
380 return NS_ERROR_INVALID_ARG
;
383 EnsureCorrectChunk(false);
385 LOG(("CacheFileInputStream::Seek() [this=%p, pos=%" PRId64
"]", this, mPos
));
390 CacheFileInputStream::SetEOF() {
391 MOZ_ASSERT(false, "Don't call SetEOF on cache input stream");
392 return NS_ERROR_NOT_IMPLEMENTED
;
397 CacheFileInputStream::Tell(int64_t* _retval
) {
398 CacheFileAutoLock
lock(mFile
);
401 LOG(("CacheFileInputStream::Tell() - Stream is closed. [this=%p]", this));
402 return NS_BASE_STREAM_CLOSED
;
407 if (mAlternativeData
) {
408 *_retval
-= mFile
->mAltDataOffset
;
411 LOG(("CacheFileInputStream::Tell() [this=%p, retval=%" PRId64
"]", this,
416 // CacheFileChunkListener
417 nsresult
CacheFileInputStream::OnChunkRead(nsresult aResult
,
418 CacheFileChunk
* aChunk
) {
419 MOZ_CRASH("CacheFileInputStream::OnChunkRead should not be called!");
420 return NS_ERROR_UNEXPECTED
;
423 nsresult
CacheFileInputStream::OnChunkWritten(nsresult aResult
,
424 CacheFileChunk
* aChunk
) {
425 MOZ_CRASH("CacheFileInputStream::OnChunkWritten should not be called!");
426 return NS_ERROR_UNEXPECTED
;
429 nsresult
CacheFileInputStream::OnChunkAvailable(nsresult aResult
,
431 CacheFileChunk
* aChunk
) {
432 CacheFileAutoLock
lock(mFile
);
434 LOG(("CacheFileInputStream::OnChunkAvailable() [this=%p, result=0x%08" PRIx32
437 this, static_cast<uint32_t>(aResult
), aChunkIdx
, aChunk
));
439 MOZ_ASSERT(mListeningForChunk
!= -1);
441 if (mListeningForChunk
!= static_cast<int64_t>(aChunkIdx
)) {
442 // This is not a chunk that we're waiting for
444 ("CacheFileInputStream::OnChunkAvailable() - Notification is for a "
445 "different chunk. [this=%p, listeningForChunk=%" PRId64
"]",
446 this, mListeningForChunk
));
452 MOZ_ASSERT(!mWaitingForUpdate
);
453 MOZ_ASSERT(!mInReadSegments
);
454 mListeningForChunk
= -1;
457 MOZ_ASSERT(!mCallback
);
460 ("CacheFileInputStream::OnChunkAvailable() - Stream is closed, "
461 "ignoring notification. [this=%p]",
467 if (NS_SUCCEEDED(aResult
)) {
469 } else if (aResult
!= NS_ERROR_NOT_AVAILABLE
) {
470 // Close the stream with error. The consumer will receive this error later
471 // in Read(), Available() etc. We need to handle NS_ERROR_NOT_AVAILABLE
472 // differently since it is returned when the requested chunk is not
473 // available and there is no writer that could create it, i.e. it means that
474 // we've reached the end of the file.
475 CloseWithStatusLocked(aResult
);
480 MaybeNotifyListener();
485 nsresult
CacheFileInputStream::OnChunkUpdated(CacheFileChunk
* aChunk
) {
486 CacheFileAutoLock
lock(mFile
);
488 LOG(("CacheFileInputStream::OnChunkUpdated() [this=%p, idx=%d]", this,
491 if (!mWaitingForUpdate
) {
493 ("CacheFileInputStream::OnChunkUpdated() - Ignoring notification since "
494 "mWaitingforUpdate == false. [this=%p]",
500 mWaitingForUpdate
= false;
502 MOZ_ASSERT(mChunk
== aChunk
);
504 MaybeNotifyListener();
509 void CacheFileInputStream::ReleaseChunk() {
510 mFile
->AssertOwnsLock();
512 LOG(("CacheFileInputStream::ReleaseChunk() [this=%p, idx=%d]", this,
515 MOZ_ASSERT(!mInReadSegments
);
517 if (mWaitingForUpdate
) {
519 ("CacheFileInputStream::ReleaseChunk() - Canceling waiting for update. "
523 mChunk
->CancelWait(this);
524 mWaitingForUpdate
= false;
527 mFile
->ReleaseOutsideLock(std::move(mChunk
));
530 void CacheFileInputStream::EnsureCorrectChunk(bool aReleaseOnly
) {
531 mFile
->AssertOwnsLock();
533 LOG(("CacheFileInputStream::EnsureCorrectChunk() [this=%p, releaseOnly=%d]",
534 this, aReleaseOnly
));
538 uint32_t chunkIdx
= mPos
/ kChunkSize
;
540 if (mInReadSegments
) {
541 // We must have correct chunk
543 MOZ_ASSERT(mChunk
->Index() == chunkIdx
);
548 if (mChunk
->Index() == chunkIdx
) {
549 // we have a correct chunk
551 ("CacheFileInputStream::EnsureCorrectChunk() - Have correct chunk "
560 MOZ_ASSERT(!mWaitingForUpdate
);
562 if (aReleaseOnly
) return;
564 if (mListeningForChunk
== static_cast<int64_t>(chunkIdx
)) {
565 // We're already waiting for this chunk
567 ("CacheFileInputStream::EnsureCorrectChunk() - Already listening for "
568 "chunk %" PRId64
" [this=%p]",
569 mListeningForChunk
, this));
574 rv
= mFile
->GetChunkLocked(chunkIdx
, CacheFile::READER
, this,
575 getter_AddRefs(mChunk
));
578 ("CacheFileInputStream::EnsureCorrectChunk() - GetChunkLocked failed. "
579 "[this=%p, idx=%d, rv=0x%08" PRIx32
"]",
580 this, chunkIdx
, static_cast<uint32_t>(rv
)));
581 if (rv
!= NS_ERROR_NOT_AVAILABLE
) {
582 // Close the stream with error. The consumer will receive this error later
583 // in Read(), Available() etc. We need to handle NS_ERROR_NOT_AVAILABLE
584 // differently since it is returned when the requested chunk is not
585 // available and there is no writer that could create it, i.e. it means
586 // that we've reached the end of the file.
587 CloseWithStatusLocked(rv
);
591 } else if (!mChunk
) {
592 mListeningForChunk
= static_cast<int64_t>(chunkIdx
);
595 MaybeNotifyListener();
598 int64_t CacheFileInputStream::CanRead(CacheFileChunkReadHandle
* aHandle
) {
599 mFile
->AssertOwnsLock();
602 MOZ_ASSERT(mPos
/ kChunkSize
== mChunk
->Index());
604 int64_t retval
= aHandle
->Offset() + aHandle
->DataSize();
606 if (!mAlternativeData
&& mFile
->mAltDataOffset
!= -1 &&
607 mFile
->mAltDataOffset
< retval
) {
608 retval
= mFile
->mAltDataOffset
;
612 if (retval
<= 0 && NS_FAILED(mChunk
->GetStatus())) {
613 CloseWithStatusLocked(mChunk
->GetStatus());
616 LOG(("CacheFileInputStream::CanRead() [this=%p, canRead=%" PRId64
"]", this,
622 void CacheFileInputStream::NotifyListener() {
623 mFile
->AssertOwnsLock();
625 LOG(("CacheFileInputStream::NotifyListener() [this=%p]", this));
627 MOZ_ASSERT(mCallback
);
628 MOZ_ASSERT(!mInReadSegments
);
630 if (!mCallbackTarget
) {
631 mCallbackTarget
= CacheFileIOManager::IOTarget();
632 if (!mCallbackTarget
) {
634 ("CacheFileInputStream::NotifyListener() - Cannot get Cache I/O "
635 "thread! Using main thread for callback."));
636 mCallbackTarget
= GetMainThreadEventTarget();
640 nsCOMPtr
<nsIInputStreamCallback
> asyncCallback
= NS_NewInputStreamReadyEvent(
641 "CacheFileInputStream::NotifyListener", mCallback
, mCallbackTarget
);
644 mCallbackTarget
= nullptr;
646 asyncCallback
->OnInputStreamReady(this);
649 void CacheFileInputStream::MaybeNotifyListener() {
650 mFile
->AssertOwnsLock();
653 ("CacheFileInputStream::MaybeNotifyListener() [this=%p, mCallback=%p, "
654 "mClosed=%d, mStatus=0x%08" PRIx32
655 ", mChunk=%p, mListeningForChunk=%" PRId64
", "
656 "mWaitingForUpdate=%d]",
657 this, mCallback
.get(), mClosed
, static_cast<uint32_t>(mStatus
),
658 mChunk
.get(), mListeningForChunk
, mWaitingForUpdate
));
660 MOZ_ASSERT(!mInReadSegments
);
662 if (!mCallback
) return;
664 if (mClosed
|| NS_FAILED(mStatus
)) {
670 if (mListeningForChunk
== -1) {
671 // EOF, should we notify even if mCallbackFlags == WAIT_CLOSURE_ONLY ??
677 MOZ_ASSERT(mPos
/ kChunkSize
== mChunk
->Index());
679 if (mWaitingForUpdate
) return;
681 CacheFileChunkReadHandle hnd
= mChunk
->GetReadHandle();
682 int64_t canRead
= CanRead(&hnd
);
683 if (NS_FAILED(mStatus
)) {
684 // CanRead() called CloseWithStatusLocked() which called
685 // MaybeNotifyListener() so the listener was already notified. Stop here.
686 MOZ_ASSERT(!mCallback
);
691 if (!(mCallbackFlags
& WAIT_CLOSURE_ONLY
)) NotifyListener();
692 } else if (canRead
== 0) {
693 if (!mFile
->OutputStreamExists(mAlternativeData
)) {
697 mChunk
->WaitForUpdate(this);
698 mWaitingForUpdate
= true;
701 // Output have set EOF before mPos?
702 MOZ_ASSERT(false, "SetEOF is currenty not implemented?!");
709 size_t CacheFileInputStream::SizeOfIncludingThis(
710 mozilla::MallocSizeOf mallocSizeOf
) const {
711 // Everything the stream keeps a reference to is already reported somewhere
712 // else. mFile reports itself. mChunk reported as part of CacheFile. mCallback
713 // is usually CacheFile or a class that is reported elsewhere.
714 return mallocSizeOf(this);
717 } // namespace mozilla::net