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
5 : *
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/
10 : *
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
14 : * License.
15 : *
16 : * The Original Code is Mozilla Communicator client code, released
17 : * March 31, 1998.
18 : *
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.
23 : *
24 : * Contributor(s):
25 : * Pierre Phaneuf <pp@ludusdesign.com>
26 : *
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.
38 : *
39 : * ***** END LICENSE BLOCK ***** */
40 :
41 : /*
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.
47 : */
48 :
49 : #include "nsAlgorithm.h"
50 : #include "nsStorageStream.h"
51 : #include "nsSegmentedBuffer.h"
52 : #include "nsStreamUtils.h"
53 : #include "nsCOMPtr.h"
54 : #include "prbit.h"
55 : #include "nsIInputStream.h"
56 : #include "nsISeekableStream.h"
57 : #include "prlog.h"
58 :
59 : #if defined(PR_LOGGING)
60 : //
61 : // Log module for StorageStream logging...
62 : //
63 : // To enable logging (see prlog.h for full details):
64 : //
65 : // set NSPR_LOG_MODULES=StorageStreamLog:5
66 : // set NSPR_LOG_FILE=nspr.log
67 : //
68 : // this enables PR_LOG_DEBUG level information and places all output in
69 : // the file nspr.log
70 : //
71 1464 : static PRLogModuleInfo* sLog = PR_NewLogModule("nsStorageStream");
72 : #endif
73 : #define LOG(args) PR_LOG(sLog, PR_LOG_DEBUG, args)
74 :
75 7820 : nsStorageStream::nsStorageStream()
76 : : mSegmentedBuffer(0), mSegmentSize(0), mWriteInProgress(false),
77 7820 : mLastSegmentNum(-1), mWriteCursor(0), mSegmentEnd(0), mLogicalLength(0)
78 : {
79 7820 : LOG(("Creating nsStorageStream [%p].\n", this));
80 7820 : }
81 :
82 7820 : nsStorageStream::~nsStorageStream()
83 : {
84 7820 : delete mSegmentedBuffer;
85 7820 : }
86 :
87 163755 : NS_IMPL_THREADSAFE_ISUPPORTS2(nsStorageStream,
88 : nsIStorageStream,
89 : nsIOutputStream)
90 :
91 : NS_IMETHODIMP
92 7820 : nsStorageStream::Init(PRUint32 segmentSize, PRUint32 maxSize,
93 : nsIMemory *segmentAllocator)
94 : {
95 7820 : mSegmentedBuffer = new nsSegmentedBuffer();
96 7820 : if (!mSegmentedBuffer)
97 0 : return NS_ERROR_OUT_OF_MEMORY;
98 :
99 7820 : mSegmentSize = segmentSize;
100 7820 : mSegmentSizeLog2 = PR_FloorLog2(segmentSize);
101 :
102 : // Segment size must be a power of two
103 7820 : if (mSegmentSize != ((PRUint32)1 << mSegmentSizeLog2))
104 0 : return NS_ERROR_INVALID_ARG;
105 :
106 7820 : return mSegmentedBuffer->Init(segmentSize, maxSize, segmentAllocator);
107 : }
108 :
109 : NS_IMETHODIMP
110 781 : nsStorageStream::GetOutputStream(PRInt32 aStartingOffset,
111 : nsIOutputStream * *aOutputStream)
112 : {
113 781 : NS_ENSURE_ARG(aOutputStream);
114 781 : NS_ENSURE_TRUE(mSegmentedBuffer, NS_ERROR_NOT_INITIALIZED);
115 :
116 781 : if (mWriteInProgress)
117 0 : return NS_ERROR_NOT_AVAILABLE;
118 :
119 781 : nsresult rv = Seek(aStartingOffset);
120 781 : if (NS_FAILED(rv)) return rv;
121 :
122 : // Enlarge the last segment in the buffer so that it is the same size as
123 : // all the other segments in the buffer. (It may have been realloc'ed
124 : // smaller in the Close() method.)
125 781 : if (mLastSegmentNum >= 0)
126 2 : if (mSegmentedBuffer->ReallocLastSegment(mSegmentSize)) {
127 : // Need to re-Seek, since realloc changed segment base pointer
128 2 : rv = Seek(aStartingOffset);
129 2 : if (NS_FAILED(rv)) return rv;
130 : }
131 :
132 781 : NS_ADDREF(this);
133 781 : *aOutputStream = static_cast<nsIOutputStream*>(this);
134 781 : mWriteInProgress = true;
135 781 : return NS_OK;
136 : }
137 :
138 : NS_IMETHODIMP
139 8197 : nsStorageStream::Close()
140 : {
141 8197 : NS_ENSURE_TRUE(mSegmentedBuffer, NS_ERROR_NOT_INITIALIZED);
142 :
143 8197 : mWriteInProgress = false;
144 :
145 8197 : PRInt32 segmentOffset = SegOffset(mLogicalLength);
146 :
147 : // Shrink the final segment in the segmented buffer to the minimum size
148 : // needed to contain the data, so as to conserve memory.
149 8197 : if (segmentOffset)
150 8119 : mSegmentedBuffer->ReallocLastSegment(segmentOffset);
151 :
152 8197 : mWriteCursor = 0;
153 8197 : mSegmentEnd = 0;
154 :
155 8197 : LOG(("nsStorageStream [%p] Close mWriteCursor=%x mSegmentEnd=%x\n",
156 : this, mWriteCursor, mSegmentEnd));
157 :
158 8197 : return NS_OK;
159 : }
160 :
161 : NS_IMETHODIMP
162 0 : nsStorageStream::Flush()
163 : {
164 0 : return NS_OK;
165 : }
166 :
167 : NS_IMETHODIMP
168 212886 : nsStorageStream::Write(const char *aBuffer, PRUint32 aCount, PRUint32 *aNumWritten)
169 : {
170 212886 : NS_ENSURE_TRUE(mSegmentedBuffer, NS_ERROR_NOT_INITIALIZED);
171 :
172 : const char* readCursor;
173 : PRUint32 count, availableInSegment, remaining;
174 212886 : nsresult rv = NS_OK;
175 :
176 212886 : NS_ENSURE_ARG_POINTER(aNumWritten);
177 212886 : NS_ENSURE_ARG(aBuffer);
178 :
179 212886 : LOG(("nsStorageStream [%p] Write mWriteCursor=%x mSegmentEnd=%x aCount=%d\n",
180 : this, mWriteCursor, mSegmentEnd, aCount));
181 :
182 212886 : remaining = aCount;
183 212886 : readCursor = aBuffer;
184 : // If no segments have been created yet, create one even if we don't have
185 : // to write any data; this enables creating an input stream which reads from
186 : // the very end of the data for any amount of data in the stream (i.e.
187 : // this stream contains N bytes of data and newInputStream(N) is called),
188 : // even for N=0 (with the caveat that we require .write("", 0) be called to
189 : // initialize internal buffers).
190 212886 : bool firstTime = mSegmentedBuffer->GetSegmentCount() == 0;
191 4296130 : while (remaining || NS_UNLIKELY(firstTime)) {
192 3870358 : firstTime = false;
193 3870358 : availableInSegment = mSegmentEnd - mWriteCursor;
194 3870358 : if (!availableInSegment) {
195 3665984 : mWriteCursor = mSegmentedBuffer->AppendNewSegment();
196 3665984 : if (!mWriteCursor) {
197 0 : mSegmentEnd = 0;
198 0 : rv = NS_ERROR_OUT_OF_MEMORY;
199 0 : goto out;
200 : }
201 3665984 : mLastSegmentNum++;
202 3665984 : mSegmentEnd = mWriteCursor + mSegmentSize;
203 3665984 : availableInSegment = mSegmentEnd - mWriteCursor;
204 3665984 : LOG(("nsStorageStream [%p] Write (new seg) mWriteCursor=%x mSegmentEnd=%x\n",
205 : this, mWriteCursor, mSegmentEnd));
206 : }
207 :
208 3870358 : count = NS_MIN(availableInSegment, remaining);
209 3870358 : memcpy(mWriteCursor, readCursor, count);
210 3870358 : remaining -= count;
211 3870358 : readCursor += count;
212 3870358 : mWriteCursor += count;
213 3870358 : LOG(("nsStorageStream [%p] Writing mWriteCursor=%x mSegmentEnd=%x count=%d\n",
214 : this, mWriteCursor, mSegmentEnd, count));
215 : };
216 :
217 : out:
218 212886 : *aNumWritten = aCount - remaining;
219 212886 : mLogicalLength += *aNumWritten;
220 :
221 212886 : LOG(("nsStorageStream [%p] Wrote mWriteCursor=%x mSegmentEnd=%x numWritten=%d\n",
222 : this, mWriteCursor, mSegmentEnd, *aNumWritten));
223 212886 : return rv;
224 : }
225 :
226 : NS_IMETHODIMP
227 0 : nsStorageStream::WriteFrom(nsIInputStream *inStr, PRUint32 count, PRUint32 *_retval)
228 : {
229 0 : return NS_ERROR_NOT_IMPLEMENTED;
230 : }
231 :
232 : NS_IMETHODIMP
233 0 : nsStorageStream::WriteSegments(nsReadSegmentFun reader, void * closure, PRUint32 count, PRUint32 *_retval)
234 : {
235 0 : return NS_ERROR_NOT_IMPLEMENTED;
236 : }
237 :
238 : NS_IMETHODIMP
239 0 : nsStorageStream::IsNonBlocking(bool *aNonBlocking)
240 : {
241 0 : *aNonBlocking = false;
242 0 : return NS_OK;
243 : }
244 :
245 : NS_IMETHODIMP
246 0 : nsStorageStream::GetLength(PRUint32 *aLength)
247 : {
248 0 : NS_ENSURE_ARG(aLength);
249 0 : *aLength = mLogicalLength;
250 0 : return NS_OK;
251 : }
252 :
253 : // Truncate the buffer by deleting the end segments
254 : NS_IMETHODIMP
255 783 : nsStorageStream::SetLength(PRUint32 aLength)
256 : {
257 783 : NS_ENSURE_TRUE(mSegmentedBuffer, NS_ERROR_NOT_INITIALIZED);
258 :
259 783 : if (mWriteInProgress)
260 0 : return NS_ERROR_NOT_AVAILABLE;
261 :
262 783 : if (aLength > mLogicalLength)
263 0 : return NS_ERROR_INVALID_ARG;
264 :
265 783 : PRInt32 newLastSegmentNum = SegNum(aLength);
266 783 : PRInt32 segmentOffset = SegOffset(aLength);
267 783 : if (segmentOffset == 0)
268 779 : newLastSegmentNum--;
269 :
270 1876 : while (newLastSegmentNum < mLastSegmentNum) {
271 310 : mSegmentedBuffer->DeleteLastSegment();
272 310 : mLastSegmentNum--;
273 : }
274 :
275 783 : mLogicalLength = aLength;
276 783 : return NS_OK;
277 : }
278 :
279 : NS_IMETHODIMP
280 7890 : nsStorageStream::GetWriteInProgress(bool *aWriteInProgress)
281 : {
282 7890 : NS_ENSURE_ARG(aWriteInProgress);
283 :
284 7890 : *aWriteInProgress = mWriteInProgress;
285 7890 : return NS_OK;
286 : }
287 :
288 : NS_METHOD
289 783 : nsStorageStream::Seek(PRInt32 aPosition)
290 : {
291 783 : NS_ENSURE_TRUE(mSegmentedBuffer, NS_ERROR_NOT_INITIALIZED);
292 :
293 : // An argument of -1 means "seek to end of stream"
294 783 : if (aPosition == -1)
295 0 : aPosition = mLogicalLength;
296 :
297 : // Seeking beyond the buffer end is illegal
298 783 : if ((PRUint32)aPosition > mLogicalLength)
299 0 : return NS_ERROR_INVALID_ARG;
300 :
301 : // Seeking backwards in the write stream results in truncation
302 783 : SetLength(aPosition);
303 :
304 : // Special handling for seek to start-of-buffer
305 783 : if (aPosition == 0) {
306 779 : mWriteCursor = 0;
307 779 : mSegmentEnd = 0;
308 779 : LOG(("nsStorageStream [%p] Seek mWriteCursor=%x mSegmentEnd=%x\n",
309 : this, mWriteCursor, mSegmentEnd));
310 779 : return NS_OK;
311 : }
312 :
313 : // Segment may have changed, so reset pointers
314 4 : mWriteCursor = mSegmentedBuffer->GetSegment(mLastSegmentNum);
315 4 : NS_ASSERTION(mWriteCursor, "null mWriteCursor");
316 4 : mSegmentEnd = mWriteCursor + mSegmentSize;
317 :
318 : // Adjust write cursor for current segment offset. This test is necessary
319 : // because SegNum may reference the next-to-be-allocated segment, in which
320 : // case we need to be pointing at the end of the last segment.
321 4 : PRInt32 segmentOffset = SegOffset(aPosition);
322 4 : if (segmentOffset == 0 && (SegNum(aPosition) > (PRUint32) mLastSegmentNum))
323 0 : mWriteCursor = mSegmentEnd;
324 : else
325 4 : mWriteCursor += segmentOffset;
326 :
327 4 : LOG(("nsStorageStream [%p] Seek mWriteCursor=%x mSegmentEnd=%x\n",
328 : this, mWriteCursor, mSegmentEnd));
329 4 : return NS_OK;
330 : }
331 :
332 : ////////////////////////////////////////////////////////////////////////////////
333 :
334 : // There can be many nsStorageInputStreams for a single nsStorageStream
335 : class nsStorageInputStream : public nsIInputStream
336 : , public nsISeekableStream
337 : {
338 : public:
339 7641 : nsStorageInputStream(nsStorageStream *aStorageStream, PRUint32 aSegmentSize)
340 : : mStorageStream(aStorageStream), mReadCursor(0),
341 : mSegmentEnd(0), mSegmentNum(0),
342 : mSegmentSize(aSegmentSize), mLogicalCursor(0),
343 7641 : mStatus(NS_OK)
344 : {
345 7641 : NS_ADDREF(mStorageStream);
346 7641 : }
347 :
348 : NS_DECL_ISUPPORTS
349 : NS_DECL_NSIINPUTSTREAM
350 : NS_DECL_NSISEEKABLESTREAM
351 :
352 : private:
353 7641 : ~nsStorageInputStream()
354 7641 : {
355 7641 : NS_IF_RELEASE(mStorageStream);
356 7641 : }
357 :
358 : protected:
359 : NS_METHOD Seek(PRUint32 aPosition);
360 :
361 : friend class nsStorageStream;
362 :
363 : private:
364 : nsStorageStream* mStorageStream;
365 : PRUint32 mReadCursor; // Next memory location to read byte, or NULL
366 : PRUint32 mSegmentEnd; // One byte past end of current buffer segment
367 : PRUint32 mSegmentNum; // Segment number containing read cursor
368 : PRUint32 mSegmentSize; // All segments, except the last, are of this size
369 : PRUint32 mLogicalCursor; // Logical offset into stream
370 : nsresult mStatus;
371 :
372 7638 : PRUint32 SegNum(PRUint32 aPosition) {return aPosition >> mStorageStream->mSegmentSizeLog2;}
373 7638 : PRUint32 SegOffset(PRUint32 aPosition) {return aPosition & (mSegmentSize - 1);}
374 : };
375 :
376 49808 : NS_IMPL_THREADSAFE_ISUPPORTS2(nsStorageInputStream,
377 : nsIInputStream,
378 : nsISeekableStream)
379 :
380 : NS_IMETHODIMP
381 7641 : nsStorageStream::NewInputStream(PRInt32 aStartingOffset, nsIInputStream* *aInputStream)
382 : {
383 7641 : NS_ENSURE_TRUE(mSegmentedBuffer, NS_ERROR_NOT_INITIALIZED);
384 :
385 7641 : nsStorageInputStream *inputStream = new nsStorageInputStream(this, mSegmentSize);
386 7641 : if (!inputStream)
387 0 : return NS_ERROR_OUT_OF_MEMORY;
388 :
389 7641 : NS_ADDREF(inputStream);
390 :
391 7641 : nsresult rv = inputStream->Seek(aStartingOffset);
392 7641 : if (NS_FAILED(rv)) {
393 0 : NS_RELEASE(inputStream);
394 0 : return rv;
395 : }
396 :
397 7641 : *aInputStream = inputStream;
398 7641 : return NS_OK;
399 : }
400 :
401 : NS_IMETHODIMP
402 193 : nsStorageInputStream::Close()
403 : {
404 193 : mStatus = NS_BASE_STREAM_CLOSED;
405 193 : return NS_OK;
406 : }
407 :
408 : NS_IMETHODIMP
409 7385 : nsStorageInputStream::Available(PRUint32 *aAvailable)
410 : {
411 7385 : if (NS_FAILED(mStatus))
412 0 : return mStatus;
413 :
414 7385 : *aAvailable = mStorageStream->mLogicalLength - mLogicalCursor;
415 7385 : return NS_OK;
416 : }
417 :
418 : NS_IMETHODIMP
419 7553 : nsStorageInputStream::Read(char* aBuffer, PRUint32 aCount, PRUint32 *aNumRead)
420 : {
421 7553 : return ReadSegments(NS_CopySegmentToBuffer, aBuffer, aCount, aNumRead);
422 : }
423 :
424 : NS_IMETHODIMP
425 7890 : nsStorageInputStream::ReadSegments(nsWriteSegmentFun writer, void * closure, PRUint32 aCount, PRUint32 *aNumRead)
426 : {
427 7890 : *aNumRead = 0;
428 7890 : if (mStatus == NS_BASE_STREAM_CLOSED)
429 0 : return NS_OK;
430 7890 : if (NS_FAILED(mStatus))
431 0 : return mStatus;
432 :
433 : PRUint32 count, availableInSegment, remainingCapacity, bytesConsumed;
434 : nsresult rv;
435 :
436 7890 : remainingCapacity = aCount;
437 3680543 : while (remainingCapacity) {
438 3665259 : availableInSegment = mSegmentEnd - mReadCursor;
439 3665259 : if (!availableInSegment) {
440 3657564 : PRUint32 available = mStorageStream->mLogicalLength - mLogicalCursor;
441 3657564 : if (!available)
442 384 : goto out;
443 :
444 3657180 : mSegmentNum++;
445 3657180 : mReadCursor = 0;
446 3657180 : mSegmentEnd = NS_MIN(mSegmentSize, available);
447 3657180 : availableInSegment = mSegmentEnd;
448 : }
449 3664875 : const char *cur = mStorageStream->mSegmentedBuffer->GetSegment(mSegmentNum);
450 :
451 3664875 : count = NS_MIN(availableInSegment, remainingCapacity);
452 : rv = writer(this, closure, cur + mReadCursor, aCount - remainingCapacity,
453 3664875 : count, &bytesConsumed);
454 3664875 : if (NS_FAILED(rv) || (bytesConsumed == 0))
455 112 : break;
456 3664763 : remainingCapacity -= bytesConsumed;
457 3664763 : mReadCursor += bytesConsumed;
458 3664763 : mLogicalCursor += bytesConsumed;
459 : };
460 :
461 : out:
462 7890 : *aNumRead = aCount - remainingCapacity;
463 :
464 7890 : bool isWriteInProgress = false;
465 7890 : if (NS_FAILED(mStorageStream->GetWriteInProgress(&isWriteInProgress)))
466 0 : isWriteInProgress = false;
467 :
468 7890 : if (*aNumRead == 0 && isWriteInProgress)
469 2 : return NS_BASE_STREAM_WOULD_BLOCK;
470 :
471 7888 : return NS_OK;
472 : }
473 :
474 : NS_IMETHODIMP
475 2 : nsStorageInputStream::IsNonBlocking(bool *aNonBlocking)
476 : {
477 : // TODO: This class should implement nsIAsyncInputStream so that callers
478 : // have some way of dealing with NS_BASE_STREAM_WOULD_BLOCK errors.
479 :
480 2 : *aNonBlocking = true;
481 2 : return NS_OK;
482 : }
483 :
484 : NS_IMETHODIMP
485 0 : nsStorageInputStream::Seek(PRInt32 aWhence, PRInt64 aOffset)
486 : {
487 0 : if (NS_FAILED(mStatus))
488 0 : return mStatus;
489 :
490 0 : PRInt64 pos = aOffset;
491 :
492 0 : switch (aWhence) {
493 : case NS_SEEK_SET:
494 0 : break;
495 : case NS_SEEK_CUR:
496 0 : pos += mLogicalCursor;
497 0 : break;
498 : case NS_SEEK_END:
499 0 : pos += mStorageStream->mLogicalLength;
500 0 : break;
501 : default:
502 0 : NS_NOTREACHED("unexpected whence value");
503 0 : return NS_ERROR_UNEXPECTED;
504 : }
505 0 : if (pos == PRInt64(mLogicalCursor))
506 0 : return NS_OK;
507 :
508 0 : return Seek(pos);
509 : }
510 :
511 : NS_IMETHODIMP
512 0 : nsStorageInputStream::Tell(PRInt64 *aResult)
513 : {
514 0 : if (NS_FAILED(mStatus))
515 0 : return mStatus;
516 :
517 0 : LL_UI2L(*aResult, mLogicalCursor);
518 0 : return NS_OK;
519 : }
520 :
521 : NS_IMETHODIMP
522 0 : nsStorageInputStream::SetEOF()
523 : {
524 0 : NS_NOTREACHED("nsStorageInputStream::SetEOF");
525 0 : return NS_ERROR_NOT_IMPLEMENTED;
526 : }
527 :
528 : NS_METHOD
529 7641 : nsStorageInputStream::Seek(PRUint32 aPosition)
530 : {
531 7641 : PRUint32 length = mStorageStream->mLogicalLength;
532 7641 : if (aPosition > length)
533 0 : return NS_ERROR_INVALID_ARG;
534 :
535 7641 : if (length == 0)
536 3 : return NS_OK;
537 :
538 7638 : mSegmentNum = SegNum(aPosition);
539 7638 : mReadCursor = SegOffset(aPosition);
540 7638 : PRUint32 available = length - aPosition;
541 7638 : mSegmentEnd = mReadCursor + NS_MIN(mSegmentSize - mReadCursor, available);
542 7638 : mLogicalCursor = aPosition;
543 7638 : return NS_OK;
544 : }
545 :
546 : nsresult
547 7590 : NS_NewStorageStream(PRUint32 segmentSize, PRUint32 maxSize, nsIStorageStream **result)
548 : {
549 7590 : NS_ENSURE_ARG(result);
550 :
551 7590 : nsStorageStream* storageStream = new nsStorageStream();
552 7590 : if (!storageStream) return NS_ERROR_OUT_OF_MEMORY;
553 :
554 7590 : NS_ADDREF(storageStream);
555 7590 : nsresult rv = storageStream->Init(segmentSize, maxSize, nsnull);
556 7590 : if (NS_FAILED(rv)) {
557 0 : NS_RELEASE(storageStream);
558 0 : return rv;
559 : }
560 7590 : *result = storageStream;
561 7590 : return NS_OK;
562 4392 : }
|