Back to index

lightning-sunbird  0.9+nobinonly
nsIncrementalDownload.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
00002 /* vim:set ts=2 sw=2 sts=2 et cindent: */
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 mozilla.org code.
00017  *
00018  * The Initial Developer of the Original Code is Google Inc.
00019  * Portions created by the Initial Developer are Copyright (C) 2005
00020  * the Initial Developer. All Rights Reserved.
00021  *
00022  * Contributor(s):
00023  *  Darin Fisher <darin@meer.net>
00024  *
00025  * Alternatively, the contents of this file may be used under the terms of
00026  * either the GNU General Public License Version 2 or later (the "GPL"), or
00027  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00028  * in which case the provisions of the GPL or the LGPL are applicable instead
00029  * of those above. If you wish to allow use of your version of this file only
00030  * under the terms of either the GPL or the LGPL, and not to allow others to
00031  * use your version of this file under the terms of the MPL, indicate your
00032  * decision by deleting the provisions above and replace them with the notice
00033  * and other provisions required by the GPL or the LGPL. If you do not delete
00034  * the provisions above, a recipient may use your version of this file under
00035  * the terms of any one of the MPL, the GPL or the LGPL.
00036  *
00037  * ***** END LICENSE BLOCK ***** */
00038 
00039 #include "nsIIncrementalDownload.h"
00040 #include "nsIRequestObserver.h"
00041 #include "nsIProgressEventSink.h"
00042 #include "nsIChannelEventSink.h"
00043 #include "nsIInterfaceRequestor.h"
00044 #include "nsIObserverService.h"
00045 #include "nsIObserver.h"
00046 #include "nsIPropertyBag2.h"
00047 #include "nsIServiceManager.h"
00048 #include "nsILocalFile.h"
00049 #include "nsITimer.h"
00050 #include "nsInt64.h"
00051 #include "nsNetUtil.h"
00052 #include "nsAutoPtr.h"
00053 #include "nsWeakReference.h"
00054 #include "nsChannelProperties.h"
00055 #include "prio.h"
00056 #include "prprf.h"
00057 
00058 // Error code used internally by the incremental downloader to cancel the
00059 // network channel when the download is already complete.
00060 #define NS_ERROR_DOWNLOAD_COMPLETE \
00061     NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_GENERAL, 1)
00062 
00063 // Error code used internally by the incremental downloader to cancel the
00064 // network channel when the response to a range request is 200 instead of 206.
00065 #define NS_ERROR_DOWNLOAD_NOT_PARTIAL \
00066     NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_GENERAL, 2)
00067 
00068 // Default values used to initialize a nsIncrementalDownload object.
00069 #define DEFAULT_CHUNK_SIZE (4096 * 16)  // bytes
00070 #define DEFAULT_INTERVAL    60          // seconds
00071 
00072 // Number of times to retry a failed byte-range request.
00073 #define MAX_RETRY_COUNT 20
00074 
00075 //-----------------------------------------------------------------------------
00076 
00077 static nsresult
00078 WriteToFile(nsILocalFile *lf, const char *data, PRUint32 len, PRInt32 flags)
00079 {
00080   PRFileDesc *fd;
00081   nsresult rv = lf->OpenNSPRFileDesc(flags, 0600, &fd);
00082   if (NS_FAILED(rv))
00083     return rv;
00084 
00085   if (len)
00086     rv = PR_Write(fd, data, len) == PRInt32(len) ? NS_OK : NS_ERROR_FAILURE;
00087 
00088   PR_Close(fd);
00089   return rv;
00090 }
00091 
00092 static nsresult
00093 AppendToFile(nsILocalFile *lf, const char *data, PRUint32 len)
00094 {
00095   PRInt32 flags = PR_WRONLY | PR_CREATE_FILE | PR_APPEND;
00096   return WriteToFile(lf, data, len, flags);
00097 }
00098 
00099 // maxSize may be -1 if unknown
00100 static void
00101 MakeRangeSpec(const nsInt64 &size, const nsInt64 &maxSize, PRInt32 chunkSize,
00102               PRBool fetchRemaining, nsCString &rangeSpec)
00103 {
00104   rangeSpec.AssignLiteral("bytes=");
00105   rangeSpec.AppendInt(PRInt64(size));
00106   rangeSpec.Append('-');
00107 
00108   if (fetchRemaining)
00109     return;
00110 
00111   nsInt64 end = size + nsInt64(chunkSize);
00112   if (maxSize != nsInt64(-1) && end > maxSize)
00113     end = maxSize;
00114   end -= 1;
00115 
00116   rangeSpec.AppendInt(PRInt64(end));
00117 }
00118 
00119 //-----------------------------------------------------------------------------
00120 
00121 class nsIncrementalDownload : public nsIIncrementalDownload
00122                             , public nsIStreamListener
00123                             , public nsIObserver
00124                             , public nsIInterfaceRequestor
00125                             , public nsIChannelEventSink
00126                             , public nsSupportsWeakReference
00127 {
00128 public:
00129   NS_DECL_ISUPPORTS
00130   NS_DECL_NSIREQUEST
00131   NS_DECL_NSIINCREMENTALDOWNLOAD
00132   NS_DECL_NSIREQUESTOBSERVER
00133   NS_DECL_NSISTREAMLISTENER
00134   NS_DECL_NSIOBSERVER
00135   NS_DECL_NSIINTERFACEREQUESTOR
00136   NS_DECL_NSICHANNELEVENTSINK
00137 
00138   nsIncrementalDownload();
00139 
00140 private:
00141   ~nsIncrementalDownload() {}
00142   nsresult FlushChunk();
00143   nsresult CallOnStartRequest();
00144   void     CallOnStopRequest();
00145   nsresult StartTimer(PRInt32 interval);
00146   nsresult ProcessTimeout();
00147   nsresult ReadCurrentSize();
00148   nsresult ClearRequestHeader(nsIHttpChannel *channel, 
00149                               const nsACString &header);
00150 
00151   nsCOMPtr<nsIRequestObserver>   mObserver;
00152   nsCOMPtr<nsISupports>          mObserverContext;
00153   nsCOMPtr<nsIProgressEventSink> mProgressSink;
00154   nsCOMPtr<nsIURI>               mURI;
00155   nsCOMPtr<nsIURI>               mFinalURI;
00156   nsCOMPtr<nsILocalFile>         mDest;
00157   nsCOMPtr<nsIChannel>           mChannel;
00158   nsCOMPtr<nsITimer>             mTimer;
00159   nsAutoArrayPtr<char>           mChunk;
00160   PRInt32                        mChunkLen;
00161   PRInt32                        mChunkSize;
00162   PRInt32                        mInterval;
00163   nsInt64                        mTotalSize;
00164   nsInt64                        mCurrentSize;
00165   PRUint32                       mLoadFlags;
00166   PRInt32                        mNonPartialCount;
00167   nsresult                       mStatus;
00168   PRPackedBool                   mIsPending;
00169   PRPackedBool                   mDidOnStartRequest;
00170 };
00171 
00172 nsIncrementalDownload::nsIncrementalDownload()
00173   : mChunkLen(0)
00174   , mChunkSize(DEFAULT_CHUNK_SIZE)
00175   , mInterval(DEFAULT_INTERVAL)
00176   , mTotalSize(-1)
00177   , mCurrentSize(-1)
00178   , mLoadFlags(LOAD_NORMAL)
00179   , mNonPartialCount(0)
00180   , mStatus(NS_OK)
00181   , mIsPending(PR_FALSE)
00182   , mDidOnStartRequest(PR_FALSE)
00183 {
00184 }
00185 
00186 nsresult
00187 nsIncrementalDownload::FlushChunk()
00188 {
00189   NS_ASSERTION(mTotalSize != nsInt64(-1), "total size should be known");
00190 
00191   if (mChunkLen == 0)
00192     return NS_OK;
00193 
00194   nsresult rv = AppendToFile(mDest, mChunk, mChunkLen);
00195   if (NS_FAILED(rv))
00196     return rv;
00197 
00198   mCurrentSize += nsInt64(mChunkLen);
00199   mChunkLen = 0;
00200 
00201   if (mProgressSink)
00202     mProgressSink->OnProgress(this, mObserverContext,
00203                               PRUint64(PRInt64(mCurrentSize)),
00204                               PRUint64(PRInt64(mTotalSize)));
00205   return NS_OK;
00206 }
00207 
00208 nsresult
00209 nsIncrementalDownload::CallOnStartRequest()
00210 {
00211   if (!mObserver || mDidOnStartRequest)
00212     return NS_OK;
00213 
00214   mDidOnStartRequest = PR_TRUE;
00215   return mObserver->OnStartRequest(this, mObserverContext);
00216 }
00217 
00218 void
00219 nsIncrementalDownload::CallOnStopRequest()
00220 {
00221   if (!mObserver)
00222     return;
00223 
00224   // Ensure that OnStartRequest is always called once before OnStopRequest.
00225   nsresult rv = CallOnStartRequest();
00226   if (NS_SUCCEEDED(mStatus))
00227     mStatus = rv;
00228 
00229   mIsPending = PR_FALSE;
00230 
00231   mObserver->OnStopRequest(this, mObserverContext, mStatus);
00232   mObserver = nsnull;
00233   mObserverContext = nsnull;
00234 }
00235 
00236 nsresult
00237 nsIncrementalDownload::StartTimer(PRInt32 interval)
00238 {
00239   nsresult rv;
00240   mTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
00241   if (NS_FAILED(rv))
00242     return rv;
00243 
00244   return mTimer->Init(this, interval * 1000, nsITimer::TYPE_ONE_SHOT);
00245 }
00246 
00247 nsresult
00248 nsIncrementalDownload::ProcessTimeout()
00249 {
00250   NS_ASSERTION(!mChannel, "how can we have a channel?");
00251 
00252   // Handle existing error conditions
00253   if (NS_FAILED(mStatus)) {
00254     CallOnStopRequest();
00255     return NS_OK;
00256   }
00257 
00258   // Fetch next chunk
00259   
00260   nsCOMPtr<nsIChannel> channel;
00261   nsresult rv = NS_NewChannel(getter_AddRefs(channel), mFinalURI, nsnull,
00262                               nsnull, this, mLoadFlags);
00263   if (NS_FAILED(rv))
00264     return rv;
00265 
00266   nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(channel, &rv);
00267   if (NS_FAILED(rv))
00268     return rv;
00269 
00270   NS_ASSERTION(mCurrentSize != nsInt64(-1),
00271       "we should know the current file size by now");
00272 
00273   // We don't support encodings -- they make the Content-Length not equal
00274   // to the actual size of the data.
00275   rv = ClearRequestHeader(http, NS_LITERAL_CSTRING("Accept-Encoding"));
00276   if (NS_FAILED(rv))
00277     return rv;
00278 
00279   // Don't bother making a range request if we are just going to fetch the
00280   // entire document.
00281   if (mInterval || mCurrentSize != nsInt64(0)) {
00282     nsCAutoString range;
00283     MakeRangeSpec(mCurrentSize, mTotalSize, mChunkSize, mInterval == 0, range);
00284 
00285     rv = http->SetRequestHeader(NS_LITERAL_CSTRING("Range"), range, PR_FALSE);
00286     if (NS_FAILED(rv))
00287       return rv;
00288   }
00289 
00290   rv = channel->AsyncOpen(this, nsnull);
00291   if (NS_FAILED(rv))
00292     return rv;
00293 
00294   // Wait to assign mChannel when we know we are going to succeed.  This is
00295   // important because we don't want to introduce a reference cycle between
00296   // mChannel and this until we know for a fact that AsyncOpen has succeeded,
00297   // thus ensuring that our stream listener methods will be invoked.
00298   mChannel = channel;
00299   return NS_OK;
00300 }
00301 
00302 // Reads the current file size and validates it.
00303 nsresult
00304 nsIncrementalDownload::ReadCurrentSize()
00305 {
00306   nsInt64 size;
00307   nsresult rv = mDest->GetFileSize((PRInt64 *) &size);
00308   if (rv == NS_ERROR_FILE_NOT_FOUND ||
00309       rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) {
00310     mCurrentSize = 0;
00311     return NS_OK;
00312   }
00313   if (NS_FAILED(rv))
00314     return rv;
00315 
00316   mCurrentSize = size; 
00317   return NS_OK;
00318 }
00319 
00320 // nsISupports
00321 
00322 NS_IMPL_ISUPPORTS8(nsIncrementalDownload,
00323                    nsIIncrementalDownload,
00324                    nsIRequest,
00325                    nsIStreamListener,
00326                    nsIRequestObserver,
00327                    nsIObserver,
00328                    nsIInterfaceRequestor,
00329                    nsIChannelEventSink,
00330                    nsISupportsWeakReference)
00331 
00332 // nsIRequest
00333 
00334 NS_IMETHODIMP
00335 nsIncrementalDownload::GetName(nsACString &name)
00336 {
00337   NS_ENSURE_TRUE(mURI, NS_ERROR_NOT_INITIALIZED);
00338 
00339   return mURI->GetSpec(name);
00340 }
00341 
00342 NS_IMETHODIMP
00343 nsIncrementalDownload::IsPending(PRBool *isPending)
00344 {
00345   *isPending = mIsPending;
00346   return NS_OK;
00347 }
00348 
00349 NS_IMETHODIMP
00350 nsIncrementalDownload::GetStatus(nsresult *status)
00351 {
00352   *status = mStatus;
00353   return NS_OK;
00354 }
00355 
00356 NS_IMETHODIMP
00357 nsIncrementalDownload::Cancel(nsresult status)
00358 {
00359   NS_ENSURE_ARG(NS_FAILED(status));
00360 
00361   // Ignore this cancelation if we're already canceled.
00362   if (NS_FAILED(mStatus))
00363     return NS_OK;
00364 
00365   mStatus = status;
00366 
00367   // Nothing more to do if callbacks aren't pending.
00368   if (!mIsPending)
00369     return NS_OK;
00370 
00371   if (mChannel) {
00372     mChannel->Cancel(mStatus);
00373     NS_ASSERTION(!mTimer, "what is this timer object doing here?");
00374   }
00375   else {
00376     // dispatch a timer callback event to drive invoking our listener's
00377     // OnStopRequest.
00378     if (mTimer)
00379       mTimer->Cancel();
00380     StartTimer(0);
00381   }
00382 
00383   return NS_OK;
00384 }
00385 
00386 NS_IMETHODIMP
00387 nsIncrementalDownload::Suspend()
00388 {
00389   return NS_ERROR_NOT_IMPLEMENTED;
00390 }
00391 
00392 NS_IMETHODIMP
00393 nsIncrementalDownload::Resume()
00394 {
00395   return NS_ERROR_NOT_IMPLEMENTED;
00396 }
00397 
00398 NS_IMETHODIMP
00399 nsIncrementalDownload::GetLoadFlags(nsLoadFlags *loadFlags)
00400 {
00401   *loadFlags = mLoadFlags;
00402   return NS_OK;
00403 }
00404 
00405 NS_IMETHODIMP
00406 nsIncrementalDownload::SetLoadFlags(nsLoadFlags loadFlags)
00407 {
00408   mLoadFlags = loadFlags;
00409   return NS_OK;
00410 }
00411 
00412 NS_IMETHODIMP
00413 nsIncrementalDownload::GetLoadGroup(nsILoadGroup **loadGroup)
00414 {
00415   return NS_ERROR_NOT_IMPLEMENTED;
00416 }
00417 
00418 NS_IMETHODIMP
00419 nsIncrementalDownload::SetLoadGroup(nsILoadGroup *loadGroup)
00420 {
00421   return NS_ERROR_NOT_IMPLEMENTED;
00422 }
00423 
00424 // nsIIncrementalDownload
00425 
00426 NS_IMETHODIMP
00427 nsIncrementalDownload::Init(nsIURI *uri, nsIFile *dest,
00428                             PRInt32 chunkSize, PRInt32 interval)
00429 {
00430   // Keep it simple: only allow initialization once
00431   NS_ENSURE_FALSE(mURI, NS_ERROR_ALREADY_INITIALIZED);
00432 
00433   mDest = do_QueryInterface(dest);
00434   NS_ENSURE_ARG(mDest);
00435 
00436   mURI = uri;
00437   mFinalURI = uri;
00438 
00439   if (chunkSize > 0)
00440     mChunkSize = chunkSize;
00441   if (interval >= 0)
00442     mInterval = interval;
00443   return NS_OK;
00444 }
00445 
00446 NS_IMETHODIMP
00447 nsIncrementalDownload::GetURI(nsIURI **result)
00448 {
00449   NS_IF_ADDREF(*result = mURI);
00450   return NS_OK;
00451 }
00452 
00453 NS_IMETHODIMP
00454 nsIncrementalDownload::GetFinalURI(nsIURI **result)
00455 {
00456   NS_IF_ADDREF(*result = mFinalURI);
00457   return NS_OK;
00458 }
00459 
00460 NS_IMETHODIMP
00461 nsIncrementalDownload::GetDestination(nsIFile **result)
00462 {
00463   if (!mDest) {
00464     *result = nsnull;
00465     return NS_OK;
00466   }
00467   // Return a clone of mDest so that callers may modify the resulting nsIFile
00468   // without corrupting our internal object.  This also works around the fact
00469   // that some nsIFile impls may cache the result of stat'ing the filesystem.
00470   return mDest->Clone(result);
00471 }
00472 
00473 NS_IMETHODIMP
00474 nsIncrementalDownload::GetTotalSize(PRInt64 *result)
00475 {
00476   *result = mTotalSize;
00477   return NS_OK;
00478 }
00479 
00480 NS_IMETHODIMP
00481 nsIncrementalDownload::GetCurrentSize(PRInt64 *result)
00482 {
00483   *result = mCurrentSize;
00484   return NS_OK;
00485 }
00486 
00487 NS_IMETHODIMP
00488 nsIncrementalDownload::Start(nsIRequestObserver *observer,
00489                              nsISupports *context)
00490 {
00491   NS_ENSURE_ARG(observer);
00492   NS_ENSURE_FALSE(mIsPending, NS_ERROR_IN_PROGRESS);
00493 
00494   // Observe system shutdown so we can be sure to release any reference held
00495   // between ourselves and the timer.  We have the observer service hold a weak
00496   // reference to us, so that we don't have to worry about calling
00497   // RemoveObserver.  XXX(darin): The timer code should do this for us.
00498   nsCOMPtr<nsIObserverService> obs =
00499       do_GetService("@mozilla.org/observer-service;1");
00500   if (obs)
00501     obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, PR_TRUE);
00502 
00503   nsresult rv = ReadCurrentSize();
00504   if (NS_FAILED(rv))
00505     return rv;
00506 
00507   rv = StartTimer(0);
00508   if (NS_FAILED(rv))
00509     return rv;
00510 
00511   mObserver = observer;
00512   mObserverContext = context;
00513   mProgressSink = do_QueryInterface(observer);  // ok if null
00514 
00515   mIsPending = PR_TRUE;
00516   return NS_OK;
00517 }
00518 
00519 // nsIRequestObserver
00520 
00521 NS_IMETHODIMP
00522 nsIncrementalDownload::OnStartRequest(nsIRequest *request,
00523                                       nsISupports *context)
00524 {
00525   nsresult rv;
00526 
00527   nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(request, &rv);
00528   if (NS_FAILED(rv))
00529     return rv;
00530 
00531   // Ensure that we are receiving a 206 response.
00532   PRUint32 code;
00533   rv = http->GetResponseStatus(&code);
00534   if (NS_FAILED(rv))
00535     return rv;
00536   if (code != 206) {
00537     // We may already have the entire file downloaded, in which case
00538     // our request for a range beyond the end of the file would have
00539     // been met with an error response code.
00540     if (code == 416 && mTotalSize == nsInt64(-1)) {
00541       mTotalSize = mCurrentSize;
00542       // Return an error code here to suppress OnDataAvailable.
00543       return NS_ERROR_DOWNLOAD_COMPLETE;
00544     }
00545     // The server may have decided to give us all of the data in one chunk.  If
00546     // we requested a partial range, then we don't want to download all of the
00547     // data at once.  So, we'll just try again, but if this keeps happening then
00548     // we'll eventually give up.
00549     if (code == 200) {
00550       if (mInterval) {
00551         mChannel = nsnull;
00552         if (++mNonPartialCount > MAX_RETRY_COUNT) {
00553           NS_WARNING("unable to fetch a byte range; giving up");
00554           return NS_ERROR_FAILURE;
00555         }
00556         // Increase delay with each failure.
00557         StartTimer(mInterval * mNonPartialCount);
00558         return NS_ERROR_DOWNLOAD_NOT_PARTIAL;
00559       }
00560       // Since we have been asked to download the rest of the file, we can deal
00561       // with a 200 response.  This may result in downloading the beginning of
00562       // the file again, but that can't really be helped.
00563     } else {
00564       NS_WARNING("server response was unexpected");
00565       return NS_ERROR_UNEXPECTED;
00566     }
00567   } else {
00568     // We got a partial response, so clear this counter in case the next chunk
00569     // results in a 200 response.
00570     mNonPartialCount = 0;
00571   }
00572 
00573   // Do special processing after the first response.
00574   if (mTotalSize == nsInt64(-1)) {
00575     // Update knowledge of mFinalURI
00576     rv = http->GetURI(getter_AddRefs(mFinalURI));
00577     if (NS_FAILED(rv))
00578       return rv;
00579 
00580     if (code == 206) {
00581       // OK, read the Content-Range header to determine the total size of this
00582       // download file.
00583       nsCAutoString buf;
00584       rv = http->GetResponseHeader(NS_LITERAL_CSTRING("Content-Range"), buf);
00585       if (NS_FAILED(rv))
00586         return rv;
00587       PRInt32 slash = buf.FindChar('/');
00588       if (slash == kNotFound) {
00589         NS_WARNING("server returned invalid Content-Range header!");
00590         return NS_ERROR_UNEXPECTED;
00591       }
00592       if (PR_sscanf(buf.get() + slash + 1, "%lld", (PRInt64 *) &mTotalSize) != 1)
00593         return NS_ERROR_UNEXPECTED;
00594     } else {
00595       // Use nsIPropertyBag2 to fetch the content length as it exposes the
00596       // value as a 64-bit number.
00597       nsCOMPtr<nsIPropertyBag2> props = do_QueryInterface(request, &rv);
00598       if (NS_FAILED(rv))
00599         return rv;
00600       rv = props->GetPropertyAsInt64(NS_CHANNEL_PROP_CONTENT_LENGTH,
00601                                      &mTotalSize.mValue);
00602       // We need to know the total size of the thing we're trying to download.
00603       if (mTotalSize == nsInt64(-1)) {
00604         NS_WARNING("server returned no content-length header!");
00605         return NS_ERROR_UNEXPECTED;
00606       }
00607       // Need to truncate (or create, if it doesn't exist) the file since we
00608       // are downloading the whole thing.
00609       WriteToFile(mDest, nsnull, 0, PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE);
00610       mCurrentSize = 0;
00611     }
00612 
00613     // Notify observer that we are starting...
00614     rv = CallOnStartRequest();
00615     if (NS_FAILED(rv))
00616       return rv;
00617   }
00618 
00619   // Adjust mChunkSize accordingly if mCurrentSize is close to mTotalSize.
00620   nsInt64 diff = mTotalSize - mCurrentSize;
00621   if (diff <= nsInt64(0)) {
00622     NS_WARNING("about to set a bogus chunk size; giving up");
00623     return NS_ERROR_UNEXPECTED;
00624   }
00625 
00626   if (diff < nsInt64(mChunkSize))
00627     mChunkSize = PRUint32(diff);
00628 
00629   mChunk = new char[mChunkSize];
00630   if (!mChunk)
00631     rv = NS_ERROR_OUT_OF_MEMORY;
00632 
00633   return rv;
00634 }
00635 
00636 NS_IMETHODIMP
00637 nsIncrementalDownload::OnStopRequest(nsIRequest *request,
00638                                      nsISupports *context,
00639                                      nsresult status)
00640 {
00641   // Not a real error; just a trick to kill off the channel without our
00642   // listener having to care.
00643   if (status == NS_ERROR_DOWNLOAD_NOT_PARTIAL)
00644     return NS_OK;
00645 
00646   // Not a real error; just a trick used to suppress OnDataAvailable calls.
00647   if (status == NS_ERROR_DOWNLOAD_COMPLETE)
00648     status = NS_OK;
00649 
00650   if (NS_SUCCEEDED(mStatus))
00651     mStatus = status;
00652 
00653   if (mChunk) {
00654     if (NS_SUCCEEDED(mStatus))
00655       mStatus = FlushChunk();
00656 
00657     mChunk = nsnull;  // deletes memory
00658     mChunkLen = 0;
00659   }
00660 
00661   mChannel = nsnull;
00662 
00663   // Notify listener if we hit an error or finished
00664   if (NS_FAILED(mStatus) || mCurrentSize == mTotalSize) {
00665     CallOnStopRequest();
00666     return NS_OK;
00667   }
00668 
00669   return StartTimer(mInterval);  // Do next chunk
00670 }
00671 
00672 // nsIStreamListener
00673 
00674 NS_IMETHODIMP
00675 nsIncrementalDownload::OnDataAvailable(nsIRequest *request,
00676                                        nsISupports *context,
00677                                        nsIInputStream *input,
00678                                        PRUint32 offset,
00679                                        PRUint32 count)
00680 {
00681   while (count) {
00682     PRUint32 space = mChunkSize - mChunkLen;
00683     PRUint32 n, len = PR_MIN(space, count);
00684 
00685     nsresult rv = input->Read(mChunk + mChunkLen, len, &n);
00686     if (NS_FAILED(rv))
00687       return rv;
00688     if (n != len)
00689       return NS_ERROR_UNEXPECTED;
00690 
00691     count -= n;
00692     mChunkLen += n;
00693 
00694     if (mChunkLen == mChunkSize)
00695       FlushChunk();
00696   }
00697 
00698   return NS_OK;
00699 }
00700 
00701 // nsIObserver
00702 
00703 NS_IMETHODIMP
00704 nsIncrementalDownload::Observe(nsISupports *subject, const char *topic,
00705                                const PRUnichar *data)
00706 {
00707   if (strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
00708     Cancel(NS_ERROR_ABORT);
00709 
00710     // Since the app is shutting down, we need to go ahead and notify our
00711     // observer here.  Otherwise, we would notify them after XPCOM has been
00712     // shutdown or not at all.
00713     CallOnStopRequest();
00714   }
00715   else if (strcmp(topic, NS_TIMER_CALLBACK_TOPIC) == 0) {
00716     mTimer = nsnull;
00717     nsresult rv = ProcessTimeout();
00718     if (NS_FAILED(rv))
00719       Cancel(rv);
00720   }
00721   return NS_OK;
00722 }
00723 
00724 // nsIInterfaceRequestor
00725 
00726 NS_IMETHODIMP
00727 nsIncrementalDownload::GetInterface(const nsIID &iid, void **result)
00728 {
00729   if (iid.Equals(NS_GET_IID(nsIChannelEventSink))) {
00730     NS_ADDREF_THIS();
00731     *result = NS_STATIC_CAST(nsIChannelEventSink *, this);
00732     return NS_OK;
00733   }
00734 
00735   nsCOMPtr<nsIInterfaceRequestor> ir = do_QueryInterface(mObserver);
00736   if (ir)
00737     return ir->GetInterface(iid, result);
00738 
00739   return NS_ERROR_NO_INTERFACE;
00740 }
00741 
00742 nsresult 
00743 nsIncrementalDownload::ClearRequestHeader(nsIHttpChannel *channel,
00744                                           const nsACString &header)
00745 {
00746   NS_ENSURE_ARG(channel);
00747   return channel->SetRequestHeader(header,
00748                                    NS_LITERAL_CSTRING(""), PR_FALSE);
00749 }
00750 
00751 // nsIChannelEventSink
00752 
00753 NS_IMETHODIMP
00754 nsIncrementalDownload::OnChannelRedirect(nsIChannel *oldChannel,
00755                                          nsIChannel *newChannel,
00756                                          PRUint32 flags)
00757 {
00758   // In response to a redirect, we need to propagate the Range header.  See bug
00759   // 311595.  Any failure code returned from this function aborts the redirect.
00760  
00761   nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(oldChannel);
00762   NS_ENSURE_STATE(http);
00763 
00764   nsCOMPtr<nsIHttpChannel> newHttpChannel = do_QueryInterface(newChannel);
00765   NS_ENSURE_STATE(newHttpChannel);
00766 
00767   NS_NAMED_LITERAL_CSTRING(rangeHdr, "Range");
00768 
00769   // We don't support encodings -- they make the Content-Length not equal
00770   // to the actual size of the data.
00771   nsresult rv = ClearRequestHeader(newHttpChannel, 
00772                                    NS_LITERAL_CSTRING("Accept-Encoding"));
00773   if (NS_FAILED(rv))
00774     return rv;
00775 
00776   // If we didn't have a Range header, then we must be doing a full download.
00777   nsCAutoString rangeVal;
00778   http->GetRequestHeader(rangeHdr, rangeVal);
00779   if (!rangeVal.IsEmpty()) {
00780     rv = newHttpChannel->SetRequestHeader(rangeHdr, rangeVal, PR_FALSE);
00781     NS_ENSURE_SUCCESS(rv, rv);
00782   }
00783 
00784   // Give the observer a chance to see this redirect notification.
00785   nsCOMPtr<nsIChannelEventSink> sink = do_GetInterface(mObserver);
00786   if (sink)
00787     rv = sink->OnChannelRedirect(oldChannel, newChannel, flags);
00788 
00789   // Update mChannel, so we can Cancel the new channel.
00790   if (NS_SUCCEEDED(rv))
00791     mChannel = newChannel;
00792 
00793   return rv;
00794 }
00795 
00796 extern NS_METHOD
00797 net_NewIncrementalDownload(nsISupports *outer, const nsIID &iid, void **result)
00798 {
00799   if (outer)
00800     return NS_ERROR_NO_AGGREGATION;
00801 
00802   nsIncrementalDownload *d = new nsIncrementalDownload();
00803   if (!d)
00804     return NS_ERROR_OUT_OF_MEMORY;
00805   
00806   NS_ADDREF(d);
00807   nsresult rv = d->QueryInterface(iid, result);
00808   NS_RELEASE(d);
00809   return rv;
00810 }