Back to index

lightning-sunbird  0.9+nobinonly
nsDiskCacheStreams.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
00002  *
00003  * ***** BEGIN LICENSE BLOCK *****
00004  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00005  *
00006  * The contents of this file are subject to the Mozilla Public License Version
00007  * 1.1 (the "License"); you may not use this file except in compliance with
00008  * the License. You may obtain a copy of the License at
00009  * http://www.mozilla.org/MPL/
00010  *
00011  * Software distributed under the License is distributed on an "AS IS" basis,
00012  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00013  * for the specific language governing rights and limitations under the
00014  * License.
00015  *
00016  * The Original Code is nsDiskCacheStreams.cpp, released
00017  * June 13, 2001.
00018  *
00019  * The Initial Developer of the Original Code is
00020  * Netscape Communications Corporation.
00021  * Portions created by the Initial Developer are Copyright (C) 2001
00022  * the Initial Developer. All Rights Reserved.
00023  *
00024  * Contributor(s):
00025  *   Gordon Sheridan <gordon@netscape.com>
00026  *
00027  * Alternatively, the contents of this file may be used under the terms of
00028  * either the GNU General Public License Version 2 or later (the "GPL"), or
00029  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00030  * in which case the provisions of the GPL or the LGPL are applicable instead
00031  * of those above. If you wish to allow use of your version of this file only
00032  * under the terms of either the GPL or the LGPL, and not to allow others to
00033  * use your version of this file under the terms of the MPL, indicate your
00034  * decision by deleting the provisions above and replace them with the notice
00035  * and other provisions required by the GPL or the LGPL. If you do not delete
00036  * the provisions above, a recipient may use your version of this file under
00037  * the terms of any one of the MPL, the GPL or the LGPL.
00038  *
00039  * ***** END LICENSE BLOCK ***** */
00040 
00041 
00042 #include "nsDiskCache.h"
00043 #include "nsDiskCacheDevice.h"
00044 #include "nsDiskCacheStreams.h"
00045 #include "nsCacheService.h"
00046 
00047 #include "nsIInputStream.h"
00048 #include "nsIOutputStream.h"
00049 #include "nsAutoLock.h"
00050 
00051 
00052 
00053 // Assumptions:
00054 //      - cache descriptors live for life of streams
00055 //      - streams will only be used by FileTransport,
00056 //         they will not be directly accessible to clients
00057 //      - overlapped I/O is NOT supported
00058 
00059 
00060 /******************************************************************************
00061  *  nsDiskCacheInputStream
00062  *****************************************************************************/
00063 #ifdef XP_MAC
00064 #pragma mark nsDiskCacheInputStream
00065 #endif
00066 class nsDiskCacheInputStream : public nsIInputStream {
00067 
00068 public:
00069 
00070     nsDiskCacheInputStream( nsDiskCacheStreamIO * parent,
00071                             PRFileDesc *          fileDesc,
00072                             const char *          buffer,
00073                             PRUint32              endOfStream);
00074 
00075     virtual ~nsDiskCacheInputStream();
00076     
00077     NS_DECL_ISUPPORTS
00078     NS_DECL_NSIINPUTSTREAM
00079 
00080 private:
00081     nsDiskCacheStreamIO *           mStreamIO;  // backpointer to parent
00082     PRFileDesc *                    mFD;
00083     const char *                    mBuffer;
00084     PRUint32                        mStreamEnd;
00085     PRUint32                        mPos;       // stream position
00086     PRBool                          mClosed;
00087 };
00088 
00089 
00090 NS_IMPL_THREADSAFE_ISUPPORTS1(nsDiskCacheInputStream, nsIInputStream)
00091 
00092 
00093 nsDiskCacheInputStream::nsDiskCacheInputStream( nsDiskCacheStreamIO * parent,
00094                                                 PRFileDesc *          fileDesc,
00095                                                 const char *          buffer,
00096                                                 PRUint32              endOfStream)
00097     : mStreamIO(parent)
00098     , mFD(fileDesc)
00099     , mBuffer(buffer)
00100     , mStreamEnd(endOfStream)
00101     , mPos(0)
00102     , mClosed(PR_FALSE)
00103 {
00104     NS_ADDREF(mStreamIO);
00105     mStreamIO->IncrementInputStreamCount();
00106 }
00107 
00108 
00109 nsDiskCacheInputStream::~nsDiskCacheInputStream()
00110 {
00111     Close();
00112     mStreamIO->DecrementInputStreamCount();
00113     NS_RELEASE(mStreamIO);
00114 }
00115 
00116 
00117 NS_IMETHODIMP
00118 nsDiskCacheInputStream::Close()
00119 {
00120     if (!mClosed) {
00121         if (mFD) {
00122             (void) PR_Close(mFD);
00123             mFD = nsnull;
00124         }
00125         mClosed = PR_TRUE;
00126     }
00127     return NS_OK;
00128 }
00129 
00130 
00131 NS_IMETHODIMP
00132 nsDiskCacheInputStream::Available(PRUint32 * bytesAvailable)
00133 {
00134     if (mClosed)  return NS_ERROR_NOT_AVAILABLE;
00135     if (mStreamEnd < mPos)  return NS_ERROR_UNEXPECTED;
00136     
00137     *bytesAvailable = mStreamEnd - mPos;
00138     return NS_OK;
00139 }
00140 
00141 
00142 NS_IMETHODIMP
00143 nsDiskCacheInputStream::Read(char * buffer, PRUint32 count, PRUint32 * bytesRead)
00144 {
00145     if (mClosed)  return NS_ERROR_NOT_AVAILABLE;
00146     
00147     *bytesRead = 0;
00148     if (mPos == mStreamEnd)  return NS_OK;
00149     if (mPos > mStreamEnd)   return NS_ERROR_UNEXPECTED;
00150     
00151     if (mFD) {
00152         // just read from file
00153         PRInt32  result = PR_Read(mFD, buffer, count);
00154         if (result < 0)  return  NS_ErrorAccordingToNSPR();
00155         
00156         mPos += (PRUint32)result;
00157         *bytesRead = (PRUint32)result;
00158         
00159     } else if (mBuffer) {
00160         // read data from mBuffer
00161         if (count > mStreamEnd - mPos)
00162             count = mStreamEnd - mPos;
00163     
00164         memcpy(buffer, mBuffer + mPos, count);
00165         mPos += count;
00166         *bytesRead = count;
00167     } else {
00168         // no data source for input stream
00169     }
00170 
00171     return NS_OK;
00172 }
00173 
00174 
00175 NS_IMETHODIMP
00176 nsDiskCacheInputStream::ReadSegments(nsWriteSegmentFun writer,
00177                                      void *            closure,
00178                                      PRUint32          count,
00179                                      PRUint32 *        bytesRead)
00180 {
00181     return NS_ERROR_NOT_IMPLEMENTED;
00182 }
00183 
00184 
00185 NS_IMETHODIMP
00186 nsDiskCacheInputStream::IsNonBlocking(PRBool * nonBlocking)
00187 {
00188     *nonBlocking = PR_FALSE;
00189     return NS_OK;
00190 }
00191 
00192 
00193 /******************************************************************************
00194  *  nsDiskCacheOutputStream
00195  *****************************************************************************/
00196 #ifdef XP_MAC
00197 #pragma mark -
00198 #pragma mark nsDiskCacheOutputStream
00199 #endif
00200 class nsDiskCacheOutputStream : public nsIOutputStream {
00201 public:
00202     nsDiskCacheOutputStream( nsDiskCacheStreamIO * parent);
00203     virtual ~nsDiskCacheOutputStream();
00204 
00205     NS_DECL_ISUPPORTS
00206     NS_DECL_NSIOUTPUTSTREAM
00207 
00208     void ReleaseStreamIO() { NS_IF_RELEASE(mStreamIO); }
00209 
00210 private:
00211     nsDiskCacheStreamIO *           mStreamIO;  // backpointer to parent
00212     PRBool                          mClosed;
00213 };
00214 
00215 
00216 NS_IMPL_THREADSAFE_ISUPPORTS1(nsDiskCacheOutputStream,
00217                               nsIOutputStream)
00218 
00219 nsDiskCacheOutputStream::nsDiskCacheOutputStream( nsDiskCacheStreamIO * parent)
00220     : mStreamIO(parent)
00221     , mClosed(PR_FALSE)
00222 {
00223     NS_ADDREF(mStreamIO);
00224 }
00225 
00226 
00227 nsDiskCacheOutputStream::~nsDiskCacheOutputStream()
00228 {
00229     Close();
00230     ReleaseStreamIO();
00231 }
00232 
00233 
00234 NS_IMETHODIMP
00235 nsDiskCacheOutputStream::Close()
00236 {
00237     if (!mClosed) {
00238         mClosed = PR_TRUE;
00239         // tell parent streamIO we are closing
00240         mStreamIO->CloseOutputStream(this);
00241     }
00242     return NS_OK;
00243 }
00244 
00245 
00246 NS_IMETHODIMP
00247 nsDiskCacheOutputStream::Flush()
00248 {
00249     if (mClosed)  return NS_ERROR_NOT_AVAILABLE;
00250     // yeah, yeah, well get to it...eventually...
00251     return NS_OK;
00252 }
00253 
00254 
00255 NS_IMETHODIMP
00256 nsDiskCacheOutputStream::Write(const char *buf, PRUint32 count, PRUint32 *bytesWritten)
00257 {
00258     if (mClosed)  return NS_ERROR_NOT_AVAILABLE;
00259     return mStreamIO->Write(buf, count, bytesWritten);
00260 }
00261 
00262 
00263 NS_IMETHODIMP
00264 nsDiskCacheOutputStream::WriteFrom(nsIInputStream *inStream, PRUint32 count, PRUint32 *bytesWritten)
00265 {
00266     NS_NOTREACHED("WriteFrom");
00267     return NS_ERROR_NOT_IMPLEMENTED;
00268 }
00269 
00270 
00271 NS_IMETHODIMP
00272 nsDiskCacheOutputStream::WriteSegments( nsReadSegmentFun reader,
00273                                         void *           closure,
00274                                         PRUint32         count,
00275                                         PRUint32 *       bytesWritten)
00276 {
00277     NS_NOTREACHED("WriteSegments");
00278     return NS_ERROR_NOT_IMPLEMENTED;
00279 }
00280 
00281 
00282 NS_IMETHODIMP
00283 nsDiskCacheOutputStream::IsNonBlocking(PRBool * nonBlocking)
00284 {
00285     *nonBlocking = PR_FALSE;
00286     return NS_OK;
00287 }
00288 
00289 
00290 
00291 /******************************************************************************
00292  *  nsDiskCacheStreamIO
00293  *****************************************************************************/
00294 #ifdef XP_MAC
00295 #pragma mark -
00296 #pragma mark nsDiskCacheStreamIO
00297 #endif
00298 
00299 NS_IMPL_THREADSAFE_ISUPPORTS0(nsDiskCacheStreamIO)
00300 
00301 // we pick 16k as the max buffer size because that is the threshold above which
00302 //      we are unable to store the data in the cache block files
00303 //      see nsDiskCacheMap.[cpp,h]
00304 #define kMaxBufferSize      (16 * 1024)
00305 
00306 nsDiskCacheStreamIO::nsDiskCacheStreamIO(nsDiskCacheBinding *   binding)
00307     : mBinding(binding)
00308     , mOutStream(nsnull)
00309     , mInStreamCount(0)
00310     , mFD(nsnull)
00311     , mStreamPos(0)
00312     , mStreamEnd(0)
00313     , mBufPos(0)
00314     , mBufEnd(0)
00315     , mBufSize(0)
00316     , mBufDirty(PR_FALSE)
00317     , mBuffer(nsnull)
00318 {
00319     mDevice = (nsDiskCacheDevice *)mBinding->mCacheEntry->CacheDevice();
00320 
00321     // acquire "death grip" on cache service
00322     nsCacheService *service = nsCacheService::GlobalInstance();
00323     NS_ADDREF(service);
00324 }
00325 
00326 
00327 nsDiskCacheStreamIO::~nsDiskCacheStreamIO()
00328 {
00329     Close();
00330 
00331     // release "death grip" on cache service
00332     nsCacheService *service = nsCacheService::GlobalInstance();
00333     NS_RELEASE(service);
00334 }
00335 
00336 
00337 void
00338 nsDiskCacheStreamIO::Close()
00339 {
00340     // this should only be called from our destructor
00341     // no one is interested in us anymore, so we don't need to grab any locks
00342     
00343     // assert streams closed
00344     NS_ASSERTION(!mOutStream, "output stream still open");
00345     NS_ASSERTION(mInStreamCount == 0, "input stream still open");
00346     NS_ASSERTION(!mFD, "file descriptor not closed");
00347 
00348     DeleteBuffer();
00349 }
00350 
00351 
00352 // NOTE: called with service lock held
00353 nsresult
00354 nsDiskCacheStreamIO::GetInputStream(PRUint32 offset, nsIInputStream ** inputStream)
00355 {
00356     NS_ENSURE_ARG_POINTER(inputStream);
00357     NS_ENSURE_TRUE(offset == 0, NS_ERROR_NOT_IMPLEMENTED);
00358 
00359     *inputStream = nsnull;
00360     
00361     if (!mBinding)  return NS_ERROR_NOT_AVAILABLE;
00362 
00363     if (mOutStream) {
00364         NS_WARNING("already have an output stream open");
00365         return NS_ERROR_NOT_AVAILABLE;
00366     }
00367 
00368     nsresult            rv;
00369     PRFileDesc *        fd = nsnull;
00370 
00371     mStreamEnd = mBinding->mCacheEntry->DataSize();
00372     if (mStreamEnd == 0) {
00373         // there's no data to read
00374         NS_ASSERTION(!mBinding->mRecord.DataLocationInitialized(), "storage allocated for zero data size");
00375     } else if (mBinding->mRecord.DataFile() == 0) {
00376         // open file desc for data
00377         rv = OpenCacheFile(PR_RDONLY, &fd);
00378         if (NS_FAILED(rv))  return rv;  // unable to open file        
00379         NS_ASSERTION(fd, "cache stream lacking open file.");
00380             
00381     } else if (!mBuffer) {
00382         // read block file for data
00383         rv = ReadCacheBlocks();
00384         if (NS_FAILED(rv))  return rv;
00385     }
00386     // else, mBuffer already contains all of the data (left over from a
00387     // previous block-file read or write).
00388 
00389     NS_ASSERTION(!(fd && mBuffer), "ambiguous data sources for input stream");
00390 
00391     // create a new input stream
00392     nsDiskCacheInputStream * inStream = new nsDiskCacheInputStream(this, fd, mBuffer, mStreamEnd);
00393     if (!inStream)  return NS_ERROR_OUT_OF_MEMORY;
00394     
00395     NS_ADDREF(*inputStream = inStream);
00396     return NS_OK;
00397 }
00398 
00399 
00400 // NOTE: called with service lock held
00401 nsresult
00402 nsDiskCacheStreamIO::GetOutputStream(PRUint32 offset, nsIOutputStream ** outputStream)
00403 {
00404     NS_ENSURE_ARG_POINTER(outputStream);
00405     *outputStream = nsnull;
00406 
00407     if (!mBinding)  return NS_ERROR_NOT_AVAILABLE;
00408         
00409     NS_ASSERTION(!mOutStream, "already have an output stream open");
00410     NS_ASSERTION(mInStreamCount == 0, "we already have input streams open");
00411     if (mOutStream || mInStreamCount)  return NS_ERROR_NOT_AVAILABLE;
00412     
00413     // mBuffer lazily allocated, but might exist if a previous stream already
00414     // created one.
00415     mBufPos    = 0;
00416     mStreamPos = 0;
00417     mStreamEnd = mBinding->mCacheEntry->DataSize();
00418 
00419     nsresult rv;
00420     if (offset) {
00421         rv = Seek(PR_SEEK_SET, offset);
00422         if (NS_FAILED(rv)) return rv;
00423     }
00424     rv = SetEOF();
00425     if (NS_FAILED(rv)) return rv;
00426 
00427     // create a new output stream
00428     mOutStream = new nsDiskCacheOutputStream(this);
00429     if (!mOutStream)  return NS_ERROR_OUT_OF_MEMORY;
00430     
00431     NS_ADDREF(*outputStream = mOutStream);
00432     return NS_OK;
00433 }
00434 
00435 void
00436 nsDiskCacheStreamIO::ClearBinding()
00437 {
00438     if (mBinding && mOutStream)
00439         Flush();
00440     mBinding = nsnull;
00441 }
00442 
00443 nsresult
00444 nsDiskCacheStreamIO::CloseOutputStream(nsDiskCacheOutputStream *  outputStream)
00445 {
00446     nsAutoLock lock(nsCacheService::ServiceLock()); // grab service lock
00447     nsresult   rv;
00448 
00449     if (outputStream != mOutStream) {
00450         NS_WARNING("mismatched output streams");
00451         return NS_ERROR_UNEXPECTED;
00452     }
00453     
00454     // output stream is closing
00455     if (!mBinding) {    // if we're severed, just clear member variables
00456         NS_ASSERTION(!mBufDirty, "oops");
00457         mOutStream = nsnull;
00458         outputStream->ReleaseStreamIO();
00459         return NS_ERROR_NOT_AVAILABLE;
00460     }
00461 
00462     rv = Flush();
00463     NS_ASSERTION(NS_SUCCEEDED(rv), "Flush() failed");
00464 
00465     mOutStream = nsnull;
00466     return rv;
00467 }
00468 
00469 nsresult
00470 nsDiskCacheStreamIO::Flush()
00471 {
00472     NS_ASSERTION(mBinding, "oops");
00473 
00474     if (!mBufDirty)
00475         return NS_OK;
00476 
00477     // write data to cache blocks, or flush mBuffer to file
00478     nsDiskCacheMap *cacheMap = mDevice->CacheMap();  // get map reference
00479     nsresult rv;
00480     
00481     if ((mStreamEnd > kMaxBufferSize) ||
00482         (mBinding->mCacheEntry->StoragePolicy() == nsICache::STORE_ON_DISK_AS_FILE)) {
00483         // make sure we save as separate file
00484         rv = FlushBufferToFile(PR_TRUE);       // will initialize DataFileLocation() if necessary
00485 
00486         if (mFD) {
00487           // close file descriptor
00488           (void) PR_Close(mFD);
00489           mFD = nsnull;
00490         }
00491         else
00492           NS_WARNING("no file descriptor");
00493 
00494         // close mFD first if possible before returning if FlushBufferToFile
00495         // failed
00496         NS_ENSURE_SUCCESS(rv, rv);
00497 
00498         // since the data location is on disk as a single file, the only value
00499         // in keeping mBuffer around is to avoid an extra malloc the next time
00500         // we need to write to this file.  reading will use a file descriptor.
00501         // therefore, it's probably not worth optimizing for the subsequent
00502         // write, so we unconditionally delete mBuffer here.
00503         DeleteBuffer();
00504 
00505     } else {
00506         // store data (if any) in cache block files
00507         
00508         // delete existing storage
00509         nsDiskCacheRecord * record = &mBinding->mRecord;
00510         if (record->DataLocationInitialized()) {
00511             rv = cacheMap->DeleteStorage(record, nsDiskCache::kData);
00512             if (NS_FAILED(rv)) {
00513                 NS_WARNING("cacheMap->DeleteStorage() failed.");
00514                 cacheMap->DoomRecord(record);
00515                 return  rv;
00516             }
00517         }
00518     
00519         // flush buffer to block files
00520         if (mStreamEnd > 0) {
00521             rv = cacheMap->WriteDataCacheBlocks(mBinding, mBuffer, mBufEnd);
00522             if (NS_FAILED(rv)) {
00523                 NS_WARNING("WriteDataCacheBlocks() failed.");
00524                 return rv;   // XXX doom cache entry?
00525                 
00526             }
00527         }
00528 
00529         mBufDirty = PR_FALSE;
00530     }
00531     
00532     // XXX do we need this here?  WriteDataCacheBlocks() calls UpdateRecord()
00533     // update cache map if entry isn't doomed
00534     if (!mBinding->mDoomed) {
00535         rv = cacheMap->UpdateRecord(&mBinding->mRecord);
00536         if (NS_FAILED(rv)) {
00537             NS_WARNING("cacheMap->UpdateRecord() failed.");
00538             return rv;   // XXX doom cache entry
00539         }
00540     }
00541     
00542     return NS_OK;
00543 }
00544 
00545 
00546 // assumptions:
00547 //      only one thread writing at a time
00548 //      never have both output and input streams open
00549 //      OnDataSizeChanged() will have already been called to update entry->DataSize()
00550 
00551 nsresult
00552 nsDiskCacheStreamIO::Write( const char * buffer,
00553                             PRUint32     count,
00554                             PRUint32 *   bytesWritten)
00555 {
00556     nsresult    rv = NS_OK;
00557     nsAutoLock lock(nsCacheService::ServiceLock()); // grab service lock
00558     if (!mBinding)  return NS_ERROR_NOT_AVAILABLE;
00559 
00560     if (mInStreamCount) {
00561         // we have open input streams already
00562         // this is an error until we support overlapped I/O
00563         NS_WARNING("Attempting to write to cache entry with open input streams.\n");
00564         return NS_ERROR_NOT_AVAILABLE;
00565     }
00566 
00567     *bytesWritten = WriteToBuffer(buffer, count);
00568     if (*bytesWritten != count)  return NS_ERROR_FAILURE;
00569 
00570     // update mStreamPos, mStreamEnd
00571     mStreamPos += count;
00572     if (mStreamEnd < mStreamPos) {
00573         mStreamEnd = mStreamPos;
00574         NS_ASSERTION(mBinding->mCacheEntry->DataSize() == mStreamEnd, "bad stream");
00575 
00576         // if we have a separate file, we need to adjust the disk cache size totals here
00577         if (mFD) {
00578             rv = UpdateFileSize();
00579         }
00580     }
00581     
00582     return rv;
00583 }
00584 
00585 
00586 nsresult
00587 nsDiskCacheStreamIO::UpdateFileSize()
00588 {
00589     NS_ASSERTION(mFD, "nsDiskCacheStreamIO::UpdateFileSize should not have been called");
00590     if (!mFD)  return NS_ERROR_UNEXPECTED;
00591     
00592     nsDiskCacheRecord * record = &mBinding->mRecord;
00593     PRUint32            oldSizeK  = record->DataFileSize();
00594     PRUint32            newSizeK  = (mStreamEnd + 0x03FF) >> 10;
00595     
00596     if (newSizeK == oldSizeK)  return NS_OK;
00597     
00598     record->SetDataFileSize(newSizeK);
00599 
00600     // update cache size totals
00601     nsDiskCacheMap * cacheMap = mDevice->CacheMap();
00602     cacheMap->DecrementTotalSize(oldSizeK * 1024);       // decrement old size
00603     cacheMap->IncrementTotalSize(newSizeK * 1024);       // increment new size
00604     
00605     if (!mBinding->mDoomed) {
00606         nsresult rv = cacheMap->UpdateRecord(&mBinding->mRecord);
00607         if (NS_FAILED(rv)) {
00608             NS_WARNING("cacheMap->UpdateRecord() failed.");
00609             // XXX doom cache entry?
00610             return rv;
00611         }
00612     }
00613     return NS_OK;
00614 }
00615 
00616 
00617 nsresult
00618 nsDiskCacheStreamIO::OpenCacheFile(PRIntn flags, PRFileDesc ** fd)
00619 {
00620     NS_ENSURE_ARG_POINTER(fd);
00621     
00622     nsresult         rv;
00623     nsDiskCacheMap * cacheMap = mDevice->CacheMap();
00624     
00625     rv = cacheMap->GetLocalFileForDiskCacheRecord(&mBinding->mRecord,
00626                                                   nsDiskCache::kData,
00627                                                   getter_AddRefs(mLocalFile));
00628     if (NS_FAILED(rv))  return rv;
00629     
00630     // create PRFileDesc for input stream - the 00600 is just for consistency
00631     rv = mLocalFile->OpenNSPRFileDesc(flags, 00600, fd);
00632     if (NS_FAILED(rv))  return rv;  // unable to open file
00633 
00634     return NS_OK;
00635 }
00636 
00637 
00638 nsresult
00639 nsDiskCacheStreamIO::ReadCacheBlocks()
00640 {
00641     NS_ASSERTION(mStreamEnd == mBinding->mCacheEntry->DataSize(), "bad stream");
00642     NS_ASSERTION(mStreamEnd <= kMaxBufferSize, "data too large for buffer");
00643 
00644     nsDiskCacheRecord * record = &mBinding->mRecord;
00645     if (!record->DataLocationInitialized()) return NS_OK;
00646 
00647     NS_ASSERTION(record->DataFile() != kSeparateFile, "attempt to read cache blocks on separate file");
00648 
00649     PRUint32 bufSize = record->DataBlockCount() * record->DataBlockSize();
00650     
00651     if (!mBuffer) {
00652         // allocate buffer
00653         mBufSize  = bufSize;
00654         mBuffer   = (char *) malloc(mBufSize);
00655         if (!mBuffer) {
00656             mBufSize = 0;
00657             return NS_ERROR_OUT_OF_MEMORY;
00658         }
00659     }
00660     NS_ASSERTION(bufSize <= mBufSize, "allocated buffer is too small");
00661     
00662     // read data stored in cache block files            
00663     nsDiskCacheMap *map = mDevice->CacheMap();  // get map reference
00664     nsresult rv = map->ReadDataCacheBlocks(mBinding, mBuffer, mBufSize);
00665     if (NS_FAILED(rv)) return rv;
00666 
00667     // update streamIO variables
00668     mBufPos = 0;
00669     mBufEnd = mStreamEnd;
00670     
00671     return NS_OK;
00672 }
00673 
00674 
00675 nsresult
00676 nsDiskCacheStreamIO::FlushBufferToFile(PRBool  clearBuffer)
00677 {
00678     nsresult  rv;
00679     nsDiskCacheRecord * record = &mBinding->mRecord;
00680     
00681     if (!mFD) {
00682         if (record->DataLocationInitialized() && (record->DataFile() > 0)) {
00683             // remove cache block storage
00684             nsDiskCacheMap * cacheMap = mDevice->CacheMap();
00685             rv = cacheMap->DeleteStorage(record, nsDiskCache::kData);
00686             if (NS_FAILED(rv))  return rv;
00687         }
00688         record->SetDataFileGeneration(mBinding->mGeneration);
00689         
00690         // allocate file
00691         rv = OpenCacheFile(PR_RDWR | PR_CREATE_FILE, &mFD);
00692         if (NS_FAILED(rv))  return rv;
00693     }
00694     
00695     // write buffer
00696     PRInt32 bytesWritten = PR_Write(mFD, mBuffer, mBufEnd);
00697     if (PRUint32(bytesWritten) != mBufEnd) {
00698         NS_WARNING("failed to flush all data");
00699         return NS_ERROR_UNEXPECTED;     // NS_ErrorAccordingToNSPR()
00700     }
00701     mBufDirty = PR_FALSE;
00702     
00703     if (clearBuffer) {
00704         // reset buffer
00705         mBufPos = 0;
00706         mBufEnd = 0;
00707     }
00708     
00709     return NS_OK;
00710 }
00711 
00712 
00713 PRUint32
00714 nsDiskCacheStreamIO::WriteToBuffer(const char * buffer, PRUint32 count)
00715 {
00716     NS_ASSERTION(count, "WriteToBuffer called with count of zero");
00717     NS_ASSERTION(mBufPos <= mBufEnd, "streamIO buffer corrupted");
00718 
00719     PRUint32 bytesLeft = count;
00720     
00721     while (bytesLeft) {
00722         if (mBufPos == mBufSize) {
00723             if (mBufSize < kMaxBufferSize) {
00724                 mBufSize = kMaxBufferSize;
00725                 mBuffer  = (char *) realloc(mBuffer, mBufSize);
00726                 if (!mBuffer)  {
00727                     mBufSize = 0;
00728                     return 0;
00729                 }
00730             } else {
00731                 nsresult rv = FlushBufferToFile(PR_TRUE);
00732                 if (NS_FAILED(rv))  return 0;
00733             }
00734         }
00735         
00736         PRUint32 chunkSize = bytesLeft;
00737         if (chunkSize > (mBufSize - mBufPos))
00738             chunkSize =  mBufSize - mBufPos;
00739         
00740         memcpy(mBuffer + mBufPos, buffer, chunkSize);
00741         mBufDirty = PR_TRUE;
00742         mBufPos += chunkSize;
00743         bytesLeft -= chunkSize;
00744         buffer += chunkSize;
00745         
00746         if (mBufEnd < mBufPos)
00747             mBufEnd = mBufPos;
00748     }
00749     
00750     return count;
00751 }
00752 
00753 void
00754 nsDiskCacheStreamIO::DeleteBuffer()
00755 {
00756     if (mBuffer) {
00757         NS_ASSERTION(mBufDirty == PR_FALSE, "deleting dirty buffer");
00758         free(mBuffer);
00759         mBuffer = nsnull;
00760         mBufPos = 0;
00761         mBufEnd = 0;
00762         mBufSize = 0;
00763     }
00764 }
00765 
00766 
00767 // NOTE: called with service lock held
00768 nsresult
00769 nsDiskCacheStreamIO::Seek(PRInt32 whence, PRInt32 offset)
00770 {
00771     PRInt32  newPos;
00772     if (!mBinding)  return NS_ERROR_NOT_AVAILABLE;
00773 
00774     if (PRUint32(offset) > mStreamEnd)  return NS_ERROR_FAILURE;
00775  
00776     if (mBinding->mRecord.DataLocationInitialized()) {
00777         if (mBinding->mRecord.DataFile() == 0) {
00778             if (!mFD) {
00779                 // we need an mFD, we better open it now
00780                 nsresult rv = OpenCacheFile(PR_RDWR | PR_CREATE_FILE, &mFD);
00781                 if (NS_FAILED(rv))  return rv;
00782             }
00783         }
00784     }
00785 
00786     if (mFD) {
00787         // do we have data in the buffer that needs to be flushed?
00788         if (mBufDirty) {
00789             // XXX optimization: are we just moving within the current buffer?
00790             nsresult rv = FlushBufferToFile(PR_TRUE);
00791             if (NS_FAILED(rv))  return rv;
00792         }
00793     
00794         newPos = PR_Seek(mFD, offset, (PRSeekWhence)whence);
00795         if (newPos == -1)
00796             return NS_ErrorAccordingToNSPR();
00797         
00798         mStreamPos = (PRUint32) newPos;
00799         mBufPos = 0;
00800         mBufEnd = 0;
00801         return NS_OK;
00802     }
00803     
00804     // else, seek in mBuffer
00805     
00806     switch(whence) {
00807         case PR_SEEK_SET:
00808             newPos = offset;
00809             break;
00810         
00811         case PR_SEEK_CUR:   // relative from current posistion
00812             newPos = offset + (PRUint32)mStreamPos;
00813             break;
00814             
00815         case PR_SEEK_END:   // relative from end
00816             newPos = offset + (PRUint32)mBufEnd;
00817             break;
00818         
00819         default:
00820             return NS_ERROR_INVALID_ARG;
00821     }
00822 
00823     // read data into mBuffer if not read yet.
00824     if (mStreamEnd && !mBufEnd) {
00825         if (newPos > 0) {
00826             nsresult rv = ReadCacheBlocks();
00827             if (NS_FAILED(rv))  return rv;
00828         }
00829     }
00830 
00831     // stream buffer sanity checks
00832     NS_ASSERTION(mBufEnd <= (16 * 1024), "bad stream");
00833     NS_ASSERTION(mBufPos <= mBufEnd,     "bad stream");
00834     NS_ASSERTION(mStreamPos == mBufPos,  "bad stream");
00835     NS_ASSERTION(mStreamEnd == mBufEnd,  "bad stream");
00836     
00837     if ((newPos < 0) || (PRUint32(newPos) > mBufEnd)) {
00838         NS_WARNING("seek offset out of range");
00839         return NS_ERROR_INVALID_ARG;
00840     }
00841 
00842     mStreamPos = newPos;
00843     mBufPos    = newPos;
00844     return NS_OK;
00845 }
00846 
00847 
00848 // called only from nsDiskCacheOutputStream::Tell
00849 nsresult
00850 nsDiskCacheStreamIO::Tell(PRUint32 * result)
00851 {
00852     NS_ENSURE_ARG_POINTER(result);
00853     *result = mStreamPos;
00854     return NS_OK;
00855 }
00856 
00857 
00858 // NOTE: called with service lock held
00859 nsresult
00860 nsDiskCacheStreamIO::SetEOF()
00861 {
00862     nsresult    rv;
00863     PRBool      needToCloseFD = PR_FALSE;
00864 
00865     NS_ASSERTION(mStreamPos <= mStreamEnd, "bad stream");
00866     if (!mBinding)  return NS_ERROR_NOT_AVAILABLE;
00867     
00868     if (mBinding->mRecord.DataLocationInitialized()) {
00869         if (mBinding->mRecord.DataFile() == 0) {
00870             if (!mFD) {
00871                 // we need an mFD, we better open it now
00872                 rv = OpenCacheFile(PR_RDWR | PR_CREATE_FILE, &mFD);
00873                 if (NS_FAILED(rv))  return rv;
00874                 needToCloseFD = PR_TRUE;
00875             }
00876         } else {
00877             // data in cache block files
00878             if ((mStreamPos != 0) && (mStreamPos != mBufPos)) {
00879                 // only read data if there will be some left after truncation
00880                 rv = ReadCacheBlocks();
00881                 if (NS_FAILED(rv))  return rv;
00882             }
00883         }
00884     }
00885     
00886     if (mFD) {
00887         rv = nsDiskCache::Truncate(mFD, mStreamPos);
00888 #ifdef DEBUG
00889         PRUint32 oldSizeK = (mStreamEnd + 0x03FF) >> 10;
00890         NS_ASSERTION(mBinding->mRecord.DataFileSize() == oldSizeK, "bad disk cache entry size");
00891     } else {
00892         // data stored in buffer.
00893         NS_ASSERTION(mStreamEnd < (16 * 1024), "buffer truncation inadequate");
00894         NS_ASSERTION(mBufPos == mStreamPos, "bad stream");
00895         NS_ASSERTION(mBuffer ? mBufEnd == mStreamEnd : PR_TRUE, "bad stream");
00896 #endif
00897     }
00898 
00899     NS_ASSERTION(mStreamEnd == mBinding->mCacheEntry->DataSize(), "cache entry not updated");
00900     // we expect nsCacheEntryDescriptor::TransportWrapper::OpenOutputStream()
00901     // to eventually update the cache entry    
00902 
00903     mStreamEnd  = mStreamPos;
00904     mBufEnd     = mBufPos;
00905     
00906     if (mFD) {
00907         UpdateFileSize();
00908         if (needToCloseFD) {
00909             (void) PR_Close(mFD);
00910             mFD = nsnull;
00911         } 
00912     }
00913 
00914     return  NS_OK;
00915 }