1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* vim:set ts=4 sts=4 sw=4 cin et: */
3 /* ***** BEGIN LICENSE BLOCK *****
4 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 * The contents of this file are subject to the Mozilla Public License Version
7 * 1.1 (the "License"); you may not use this file except in compliance with
8 * the License. You may obtain a copy of the License at
9 * http://www.mozilla.org/MPL/
11 * Software distributed under the License is distributed on an "AS IS" basis,
12 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 * for the specific language governing rights and limitations under the
16 * The Original Code is Mozilla Communicator client code, released
19 * The Initial Developer of the Original Code is
20 * Netscape Communications Corporation.
21 * Portions created by the Initial Developer are Copyright (C) 1998-1999
22 * the Initial Developer. All Rights Reserved.
25 * Pierre Phaneuf <pp@ludusdesign.com>
27 * Alternatively, the contents of this file may be used under the terms of
28 * either of the GNU General Public License Version 2 or later (the "GPL"),
29 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
30 * in which case the provisions of the GPL or the LGPL are applicable instead
31 * of those above. If you wish to allow use of your version of this file only
32 * under the terms of either the GPL or the LGPL, and not to allow others to
33 * use your version of this file under the terms of the MPL, indicate your
34 * decision by deleting the provisions above and replace them with the notice
35 * and other provisions required by the GPL or the LGPL. If you do not delete
36 * the provisions above, a recipient may use your version of this file under
37 * the terms of any one of the MPL, the GPL or the LGPL.
39 * ***** END LICENSE BLOCK ***** */
42 * The storage stream provides an internal buffer that can be filled by a
43 * client using a single output stream. One or more independent input streams
44 * can be created to read the data out non-destructively. The implementation
45 * uses a segmented buffer internally to avoid realloc'ing of large buffers,
46 * with the attendant performance loss and heap fragmentation.
49 #include "nsStorageStream.h"
50 #include "nsSegmentedBuffer.h"
51 #include "nsStreamUtils.h"
54 #include "nsIInputStream.h"
55 #include "nsISeekableStream.h"
59 #if defined(PR_LOGGING)
61 // Log module for StorageStream logging...
63 // To enable logging (see prlog.h for full details):
65 // set NSPR_LOG_MODULES=StorageStreamLog:5
66 // set NSPR_LOG_FILE=nspr.log
68 // this enables PR_LOG_DEBUG level information and places all output in
71 static PRLogModuleInfo
* sLog
= PR_NewLogModule("nsStorageStream");
73 #define LOG(args) PR_LOG(sLog, PR_LOG_DEBUG, args)
75 nsStorageStream::nsStorageStream()
76 : mSegmentedBuffer(0), mSegmentSize(0), mWriteInProgress(PR_FALSE
),
77 mLastSegmentNum(-1), mWriteCursor(0), mSegmentEnd(0), mLogicalLength(0)
79 LOG(("Creating nsStorageStream [%p].\n", this));
82 nsStorageStream::~nsStorageStream()
85 delete mSegmentedBuffer
;
88 NS_IMPL_THREADSAFE_ISUPPORTS2(nsStorageStream
,
93 nsStorageStream::Init(PRUint32 segmentSize
, PRUint32 maxSize
,
94 nsIMemory
*segmentAllocator
)
96 mSegmentedBuffer
= new nsSegmentedBuffer();
97 if (!mSegmentedBuffer
)
98 return NS_ERROR_OUT_OF_MEMORY
;
100 mSegmentSize
= segmentSize
;
101 mSegmentSizeLog2
= PR_FloorLog2(segmentSize
);
103 // Segment size must be a power of two
104 if (mSegmentSize
!= ((PRUint32
)1 << mSegmentSizeLog2
))
105 return NS_ERROR_INVALID_ARG
;
107 return mSegmentedBuffer
->Init(segmentSize
, maxSize
, segmentAllocator
);
111 nsStorageStream::GetOutputStream(PRInt32 aStartingOffset
,
112 nsIOutputStream
* *aOutputStream
)
114 NS_ENSURE_ARG(aOutputStream
);
115 NS_ENSURE_TRUE(mSegmentedBuffer
, NS_ERROR_NOT_INITIALIZED
);
117 if (mWriteInProgress
)
118 return NS_ERROR_NOT_AVAILABLE
;
120 nsresult rv
= Seek(aStartingOffset
);
121 if (NS_FAILED(rv
)) return rv
;
123 // Enlarge the last segment in the buffer so that it is the same size as
124 // all the other segments in the buffer. (It may have been realloc'ed
125 // smaller in the Close() method.)
126 if (mLastSegmentNum
>= 0)
127 mSegmentedBuffer
->ReallocLastSegment(mSegmentSize
);
129 // Need to re-Seek, since realloc might have changed segment base pointer
130 rv
= Seek(aStartingOffset
);
131 if (NS_FAILED(rv
)) return rv
;
134 *aOutputStream
= static_cast<nsIOutputStream
*>(this);
135 mWriteInProgress
= PR_TRUE
;
140 nsStorageStream::Close()
142 NS_ENSURE_TRUE(mSegmentedBuffer
, NS_ERROR_NOT_INITIALIZED
);
144 mWriteInProgress
= PR_FALSE
;
146 PRInt32 segmentOffset
= SegOffset(mLogicalLength
);
148 // Shrink the final segment in the segmented buffer to the minimum size
149 // needed to contain the data, so as to conserve memory.
151 mSegmentedBuffer
->ReallocLastSegment(segmentOffset
);
156 LOG(("nsStorageStream [%p] Close mWriteCursor=%x mSegmentEnd=%x\n",
157 this, mWriteCursor
, mSegmentEnd
));
163 nsStorageStream::Flush()
169 nsStorageStream::Write(const char *aBuffer
, PRUint32 aCount
, PRUint32
*aNumWritten
)
171 NS_ENSURE_TRUE(mSegmentedBuffer
, NS_ERROR_NOT_INITIALIZED
);
173 const char* readCursor
;
174 PRUint32 count
, availableInSegment
, remaining
;
177 NS_ENSURE_ARG_POINTER(aNumWritten
);
178 NS_ENSURE_ARG(aBuffer
);
180 LOG(("nsStorageStream [%p] Write mWriteCursor=%x mSegmentEnd=%x aCount=%d\n",
181 this, mWriteCursor
, mSegmentEnd
, aCount
));
184 readCursor
= aBuffer
;
185 // If no segments have been created yet, create one even if we don't have
186 // to write any data; this enables creating an input stream which reads from
187 // the very end of the data for any amount of data in the stream (i.e.
188 // this stream contains N bytes of data and newInputStream(N) is called),
189 // even for N=0 (with the caveat that we require .write("", 0) be called to
190 // initialize internal buffers).
191 PRBool firstTime
= mSegmentedBuffer
->GetSegmentCount() == 0;
192 while (remaining
|| NS_UNLIKELY(firstTime
)) {
193 firstTime
= PR_FALSE
;
194 availableInSegment
= mSegmentEnd
- mWriteCursor
;
195 if (!availableInSegment
) {
196 mWriteCursor
= mSegmentedBuffer
->AppendNewSegment();
199 rv
= NS_ERROR_OUT_OF_MEMORY
;
203 mSegmentEnd
= mWriteCursor
+ mSegmentSize
;
204 availableInSegment
= mSegmentEnd
- mWriteCursor
;
205 LOG(("nsStorageStream [%p] Write (new seg) mWriteCursor=%x mSegmentEnd=%x\n",
206 this, mWriteCursor
, mSegmentEnd
));
209 count
= PR_MIN(availableInSegment
, remaining
);
210 memcpy(mWriteCursor
, readCursor
, count
);
213 mWriteCursor
+= count
;
214 LOG(("nsStorageStream [%p] Writing mWriteCursor=%x mSegmentEnd=%x count=%d\n",
215 this, mWriteCursor
, mSegmentEnd
, count
));
219 *aNumWritten
= aCount
- remaining
;
220 mLogicalLength
+= *aNumWritten
;
222 LOG(("nsStorageStream [%p] Wrote mWriteCursor=%x mSegmentEnd=%x numWritten=%d\n",
223 this, mWriteCursor
, mSegmentEnd
, *aNumWritten
));
228 nsStorageStream::WriteFrom(nsIInputStream
*inStr
, PRUint32 count
, PRUint32
*_retval
)
230 NS_NOTREACHED("WriteFrom");
231 return NS_ERROR_NOT_IMPLEMENTED
;
235 nsStorageStream::WriteSegments(nsReadSegmentFun reader
, void * closure
, PRUint32 count
, PRUint32
*_retval
)
237 NS_NOTREACHED("WriteSegments");
238 return NS_ERROR_NOT_IMPLEMENTED
;
242 nsStorageStream::IsNonBlocking(PRBool
*aNonBlocking
)
244 *aNonBlocking
= PR_TRUE
;
249 nsStorageStream::GetLength(PRUint32
*aLength
)
251 NS_ENSURE_ARG(aLength
);
252 *aLength
= mLogicalLength
;
256 // Truncate the buffer by deleting the end segments
258 nsStorageStream::SetLength(PRUint32 aLength
)
260 NS_ENSURE_TRUE(mSegmentedBuffer
, NS_ERROR_NOT_INITIALIZED
);
262 if (mWriteInProgress
)
263 return NS_ERROR_NOT_AVAILABLE
;
265 if (aLength
> mLogicalLength
)
266 return NS_ERROR_INVALID_ARG
;
268 PRInt32 newLastSegmentNum
= SegNum(aLength
);
269 PRInt32 segmentOffset
= SegOffset(aLength
);
270 if (segmentOffset
== 0)
273 while (newLastSegmentNum
< mLastSegmentNum
) {
274 mSegmentedBuffer
->DeleteLastSegment();
278 mLogicalLength
= aLength
;
283 nsStorageStream::GetWriteInProgress(PRBool
*aWriteInProgress
)
285 NS_ENSURE_ARG(aWriteInProgress
);
287 *aWriteInProgress
= mWriteInProgress
;
292 nsStorageStream::Seek(PRInt32 aPosition
)
294 NS_ENSURE_TRUE(mSegmentedBuffer
, NS_ERROR_NOT_INITIALIZED
);
296 // An argument of -1 means "seek to end of stream"
298 aPosition
= mLogicalLength
;
300 // Seeking beyond the buffer end is illegal
301 if ((PRUint32
)aPosition
> mLogicalLength
)
302 return NS_ERROR_INVALID_ARG
;
304 // Seeking backwards in the write stream results in truncation
305 SetLength(aPosition
);
307 // Special handling for seek to start-of-buffer
308 if (aPosition
== 0) {
311 LOG(("nsStorageStream [%p] Seek mWriteCursor=%x mSegmentEnd=%x\n",
312 this, mWriteCursor
, mSegmentEnd
));
316 // Segment may have changed, so reset pointers
317 mWriteCursor
= mSegmentedBuffer
->GetSegment(mLastSegmentNum
);
318 NS_ASSERTION(mWriteCursor
, "null mWriteCursor");
319 mSegmentEnd
= mWriteCursor
+ mSegmentSize
;
321 // Adjust write cursor for current segment offset. This test is necessary
322 // because SegNum may reference the next-to-be-allocated segment, in which
323 // case we need to be pointing at the end of the last segment.
324 PRInt32 segmentOffset
= SegOffset(aPosition
);
325 if (segmentOffset
== 0 && (SegNum(aPosition
) > (PRUint32
) mLastSegmentNum
))
326 mWriteCursor
= mSegmentEnd
;
328 mWriteCursor
+= segmentOffset
;
330 LOG(("nsStorageStream [%p] Seek mWriteCursor=%x mSegmentEnd=%x\n",
331 this, mWriteCursor
, mSegmentEnd
));
335 ////////////////////////////////////////////////////////////////////////////////
337 // There can be many nsStorageInputStreams for a single nsStorageStream
338 class nsStorageInputStream
: public nsIInputStream
339 , public nsISeekableStream
342 nsStorageInputStream(nsStorageStream
*aStorageStream
, PRUint32 aSegmentSize
)
343 : mStorageStream(aStorageStream
), mReadCursor(0),
344 mSegmentEnd(0), mSegmentNum(0),
345 mSegmentSize(aSegmentSize
), mLogicalCursor(0),
348 NS_ADDREF(mStorageStream
);
352 NS_DECL_NSIINPUTSTREAM
353 NS_DECL_NSISEEKABLESTREAM
356 ~nsStorageInputStream()
358 NS_IF_RELEASE(mStorageStream
);
362 NS_METHOD
Seek(PRUint32 aPosition
);
364 friend class nsStorageStream
;
367 nsStorageStream
* mStorageStream
;
368 const char* mReadCursor
; // Next memory location to read byte, or NULL
369 const char* mSegmentEnd
; // One byte past end of current buffer segment
370 PRUint32 mSegmentNum
; // Segment number containing read cursor
371 PRUint32 mSegmentSize
; // All segments, except the last, are of this size
372 PRUint32 mLogicalCursor
; // Logical offset into stream
375 PRUint32
SegNum(PRUint32 aPosition
) {return aPosition
>> mStorageStream
->mSegmentSizeLog2
;}
376 PRUint32
SegOffset(PRUint32 aPosition
) {return aPosition
& (mSegmentSize
- 1);}
379 NS_IMPL_THREADSAFE_ISUPPORTS2(nsStorageInputStream
,
384 nsStorageStream::NewInputStream(PRInt32 aStartingOffset
, nsIInputStream
* *aInputStream
)
386 NS_ENSURE_TRUE(mSegmentedBuffer
, NS_ERROR_NOT_INITIALIZED
);
388 nsStorageInputStream
*inputStream
= new nsStorageInputStream(this, mSegmentSize
);
390 return NS_ERROR_OUT_OF_MEMORY
;
392 NS_ADDREF(inputStream
);
394 nsresult rv
= inputStream
->Seek(aStartingOffset
);
396 NS_RELEASE(inputStream
);
400 *aInputStream
= inputStream
;
405 nsStorageInputStream::Close()
407 mStatus
= NS_BASE_STREAM_CLOSED
;
412 nsStorageInputStream::Available(PRUint32
*aAvailable
)
414 if (NS_FAILED(mStatus
))
417 *aAvailable
= mStorageStream
->mLogicalLength
- mLogicalCursor
;
422 nsStorageInputStream::Read(char* aBuffer
, PRUint32 aCount
, PRUint32
*aNumRead
)
424 return ReadSegments(NS_CopySegmentToBuffer
, aBuffer
, aCount
, aNumRead
);
428 nsStorageInputStream::ReadSegments(nsWriteSegmentFun writer
, void * closure
, PRUint32 aCount
, PRUint32
*aNumRead
)
431 if (mStatus
== NS_BASE_STREAM_CLOSED
)
433 if (NS_FAILED(mStatus
))
436 PRUint32 count
, availableInSegment
, remainingCapacity
, bytesConsumed
;
439 remainingCapacity
= aCount
;
440 while (remainingCapacity
) {
441 availableInSegment
= mSegmentEnd
- mReadCursor
;
442 if (!availableInSegment
) {
443 PRUint32 available
= mStorageStream
->mLogicalLength
- mLogicalCursor
;
447 mReadCursor
= mStorageStream
->mSegmentedBuffer
->GetSegment(++mSegmentNum
);
448 mSegmentEnd
= mReadCursor
+ PR_MIN(mSegmentSize
, available
);
449 availableInSegment
= mSegmentEnd
- mReadCursor
;
452 count
= PR_MIN(availableInSegment
, remainingCapacity
);
453 rv
= writer(this, closure
, mReadCursor
, aCount
- remainingCapacity
,
454 count
, &bytesConsumed
);
455 if (NS_FAILED(rv
) || (bytesConsumed
== 0))
457 remainingCapacity
-= bytesConsumed
;
458 mReadCursor
+= bytesConsumed
;
459 mLogicalCursor
+= bytesConsumed
;
463 *aNumRead
= aCount
- remainingCapacity
;
465 PRBool isWriteInProgress
= PR_FALSE
;
466 if (NS_FAILED(mStorageStream
->GetWriteInProgress(&isWriteInProgress
)))
467 isWriteInProgress
= PR_FALSE
;
469 if (*aNumRead
== 0 && isWriteInProgress
)
470 return NS_BASE_STREAM_WOULD_BLOCK
;
476 nsStorageInputStream::IsNonBlocking(PRBool
*aNonBlocking
)
478 // TODO: This class should implement nsIAsyncInputStream so that callers
479 // have some way of dealing with NS_BASE_STREAM_WOULD_BLOCK errors.
481 *aNonBlocking
= PR_TRUE
;
486 nsStorageInputStream::Seek(PRInt32 aWhence
, PRInt64 aOffset
)
488 if (NS_FAILED(mStatus
))
491 PRInt64 pos
= aOffset
;
497 pos
+= mLogicalCursor
;
500 pos
+= mStorageStream
->mLogicalLength
;
503 NS_NOTREACHED("unexpected whence value");
504 return NS_ERROR_UNEXPECTED
;
506 if (pos
== PRInt64(mLogicalCursor
))
513 nsStorageInputStream::Tell(PRInt64
*aResult
)
515 if (NS_FAILED(mStatus
))
518 LL_UI2L(*aResult
, mLogicalCursor
);
523 nsStorageInputStream::SetEOF()
525 NS_NOTREACHED("nsStorageInputStream::SetEOF");
526 return NS_ERROR_NOT_IMPLEMENTED
;
530 nsStorageInputStream::Seek(PRUint32 aPosition
)
532 PRUint32 length
= mStorageStream
->mLogicalLength
;
533 if (aPosition
> length
)
534 return NS_ERROR_INVALID_ARG
;
539 mSegmentNum
= SegNum(aPosition
);
540 PRUint32 segmentOffset
= SegOffset(aPosition
);
541 mReadCursor
= mStorageStream
->mSegmentedBuffer
->GetSegment(mSegmentNum
) +
543 PRUint32 available
= length
- aPosition
;
544 mSegmentEnd
= mReadCursor
+ PR_MIN(mSegmentSize
- segmentOffset
, available
);
545 mLogicalCursor
= aPosition
;
550 NS_NewStorageStream(PRUint32 segmentSize
, PRUint32 maxSize
, nsIStorageStream
**result
)
552 NS_ENSURE_ARG(result
);
554 nsStorageStream
* storageStream
= new nsStorageStream();
555 if (!storageStream
) return NS_ERROR_OUT_OF_MEMORY
;
557 NS_ADDREF(storageStream
);
558 nsresult rv
= storageStream
->Init(segmentSize
, maxSize
, nsnull
);
560 NS_RELEASE(storageStream
);
563 *result
= storageStream
;