LCOV - code coverage report
Current view: directory - netwerk/base/src - nsIncrementalDownload.cpp (source / functions) Found Hit Coverage
Test: app.info Lines: 343 226 65.9 %
Date: 2012-06-02 Functions: 41 28 68.3 %

       1                 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
       2                 : /* vim:set ts=2 sw=2 sts=2 et cindent: */
       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.org code.
      17                 :  *
      18                 :  * The Initial Developer of the Original Code is Google Inc.
      19                 :  * Portions created by the Initial Developer are Copyright (C) 2005
      20                 :  * the Initial Developer. All Rights Reserved.
      21                 :  *
      22                 :  * Contributor(s):
      23                 :  *  Darin Fisher <darin@meer.net>
      24                 :  *
      25                 :  * Alternatively, the contents of this file may be used under the terms of
      26                 :  * either the GNU General Public License Version 2 or later (the "GPL"), or
      27                 :  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
      28                 :  * in which case the provisions of the GPL or the LGPL are applicable instead
      29                 :  * of those above. If you wish to allow use of your version of this file only
      30                 :  * under the terms of either the GPL or the LGPL, and not to allow others to
      31                 :  * use your version of this file under the terms of the MPL, indicate your
      32                 :  * decision by deleting the provisions above and replace them with the notice
      33                 :  * and other provisions required by the GPL or the LGPL. If you do not delete
      34                 :  * the provisions above, a recipient may use your version of this file under
      35                 :  * the terms of any one of the MPL, the GPL or the LGPL.
      36                 :  *
      37                 :  * ***** END LICENSE BLOCK ***** */
      38                 : 
      39                 : #include "mozilla/Attributes.h"
      40                 : 
      41                 : #include "nsIIncrementalDownload.h"
      42                 : #include "nsIRequestObserver.h"
      43                 : #include "nsIProgressEventSink.h"
      44                 : #include "nsIChannelEventSink.h"
      45                 : #include "nsIAsyncVerifyRedirectCallback.h"
      46                 : #include "nsIInterfaceRequestor.h"
      47                 : #include "nsIObserverService.h"
      48                 : #include "nsIObserver.h"
      49                 : #include "nsIPropertyBag2.h"
      50                 : #include "nsIServiceManager.h"
      51                 : #include "nsILocalFile.h"
      52                 : #include "nsITimer.h"
      53                 : #include "nsNetUtil.h"
      54                 : #include "nsAutoPtr.h"
      55                 : #include "nsWeakReference.h"
      56                 : #include "nsChannelProperties.h"
      57                 : #include "prio.h"
      58                 : #include "prprf.h"
      59                 : 
      60                 : // Error code used internally by the incremental downloader to cancel the
      61                 : // network channel when the download is already complete.
      62                 : #define NS_ERROR_DOWNLOAD_COMPLETE \
      63                 :     NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_GENERAL, 1)
      64                 : 
      65                 : // Error code used internally by the incremental downloader to cancel the
      66                 : // network channel when the response to a range request is 200 instead of 206.
      67                 : #define NS_ERROR_DOWNLOAD_NOT_PARTIAL \
      68                 :     NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_GENERAL, 2)
      69                 : 
      70                 : // Default values used to initialize a nsIncrementalDownload object.
      71                 : #define DEFAULT_CHUNK_SIZE (4096 * 16)  // bytes
      72                 : #define DEFAULT_INTERVAL    60          // seconds
      73                 : 
      74                 : #define UPDATE_PROGRESS_INTERVAL PRTime(500 * PR_USEC_PER_MSEC) // 500ms
      75                 : 
      76                 : // Number of times to retry a failed byte-range request.
      77                 : #define MAX_RETRY_COUNT 20
      78                 : 
      79                 : //-----------------------------------------------------------------------------
      80                 : 
      81                 : static nsresult
      82              10 : WriteToFile(nsILocalFile *lf, const char *data, PRUint32 len, PRInt32 flags)
      83                 : {
      84                 :   PRFileDesc *fd;
      85              10 :   nsresult rv = lf->OpenNSPRFileDesc(flags, 0600, &fd);
      86              10 :   if (NS_FAILED(rv))
      87               0 :     return rv;
      88                 : 
      89              10 :   if (len)
      90               5 :     rv = PR_Write(fd, data, len) == PRInt32(len) ? NS_OK : NS_ERROR_FAILURE;
      91                 : 
      92              10 :   PR_Close(fd);
      93              10 :   return rv;
      94                 : }
      95                 : 
      96                 : static nsresult
      97               5 : AppendToFile(nsILocalFile *lf, const char *data, PRUint32 len)
      98                 : {
      99               5 :   PRInt32 flags = PR_WRONLY | PR_CREATE_FILE | PR_APPEND;
     100               5 :   return WriteToFile(lf, data, len, flags);
     101                 : }
     102                 : 
     103                 : // maxSize may be -1 if unknown
     104                 : static void
     105               6 : MakeRangeSpec(const PRInt64 &size, const PRInt64 &maxSize, PRInt32 chunkSize,
     106                 :               bool fetchRemaining, nsCString &rangeSpec)
     107                 : {
     108               6 :   rangeSpec.AssignLiteral("bytes=");
     109               6 :   rangeSpec.AppendInt(PRInt64(size));
     110               6 :   rangeSpec.Append('-');
     111                 : 
     112               6 :   if (fetchRemaining)
     113               5 :     return;
     114                 : 
     115               1 :   PRInt64 end = size + PRInt64(chunkSize);
     116               1 :   if (maxSize != PRInt64(-1) && end > maxSize)
     117               0 :     end = maxSize;
     118               1 :   end -= 1;
     119                 : 
     120               1 :   rangeSpec.AppendInt(PRInt64(end));
     121                 : }
     122                 : 
     123                 : //-----------------------------------------------------------------------------
     124                 : 
     125                 : class nsIncrementalDownload MOZ_FINAL
     126                 :   : public nsIIncrementalDownload
     127                 :   , public nsIStreamListener
     128                 :   , public nsIObserver
     129                 :   , public nsIInterfaceRequestor
     130                 :   , public nsIChannelEventSink
     131                 :   , public nsSupportsWeakReference
     132                 :   , public nsIAsyncVerifyRedirectCallback
     133                 : {
     134                 : public:
     135                 :   NS_DECL_ISUPPORTS
     136                 :   NS_DECL_NSIREQUEST
     137                 :   NS_DECL_NSIINCREMENTALDOWNLOAD
     138                 :   NS_DECL_NSIREQUESTOBSERVER
     139                 :   NS_DECL_NSISTREAMLISTENER
     140                 :   NS_DECL_NSIOBSERVER
     141                 :   NS_DECL_NSIINTERFACEREQUESTOR
     142                 :   NS_DECL_NSICHANNELEVENTSINK
     143                 :   NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK
     144                 : 
     145                 :   nsIncrementalDownload();
     146                 : 
     147                 : private:
     148              13 :   ~nsIncrementalDownload() {}
     149                 :   nsresult FlushChunk();
     150                 :   void     UpdateProgress();
     151                 :   nsresult CallOnStartRequest();
     152                 :   void     CallOnStopRequest();
     153                 :   nsresult StartTimer(PRInt32 interval);
     154                 :   nsresult ProcessTimeout();
     155                 :   nsresult ReadCurrentSize();
     156                 :   nsresult ClearRequestHeader(nsIHttpChannel *channel);
     157                 : 
     158                 :   nsCOMPtr<nsIRequestObserver>             mObserver;
     159                 :   nsCOMPtr<nsISupports>                    mObserverContext;
     160                 :   nsCOMPtr<nsIProgressEventSink>           mProgressSink;
     161                 :   nsCOMPtr<nsIURI>                         mURI;
     162                 :   nsCOMPtr<nsIURI>                         mFinalURI;
     163                 :   nsCOMPtr<nsILocalFile>                   mDest;
     164                 :   nsCOMPtr<nsIChannel>                     mChannel;
     165                 :   nsCOMPtr<nsITimer>                       mTimer;
     166                 :   nsAutoArrayPtr<char>                     mChunk;
     167                 :   PRInt32                                  mChunkLen;
     168                 :   PRInt32                                  mChunkSize;
     169                 :   PRInt32                                  mInterval;
     170                 :   PRInt64                                  mTotalSize;
     171                 :   PRInt64                                  mCurrentSize;
     172                 :   PRUint32                                 mLoadFlags;
     173                 :   PRInt32                                  mNonPartialCount;
     174                 :   nsresult                                 mStatus;
     175                 :   bool                                     mIsPending;
     176                 :   bool                                     mDidOnStartRequest;
     177                 :   PRTime                                   mLastProgressUpdate;
     178                 :   nsCOMPtr<nsIAsyncVerifyRedirectCallback> mRedirectCallback;
     179                 :   nsCOMPtr<nsIChannel>                     mNewRedirectChannel;
     180                 : };
     181                 : 
     182              13 : nsIncrementalDownload::nsIncrementalDownload()
     183                 :   : mChunkLen(0)
     184                 :   , mChunkSize(DEFAULT_CHUNK_SIZE)
     185                 :   , mInterval(DEFAULT_INTERVAL)
     186                 :   , mTotalSize(-1)
     187                 :   , mCurrentSize(-1)
     188                 :   , mLoadFlags(LOAD_NORMAL)
     189                 :   , mNonPartialCount(0)
     190                 :   , mStatus(NS_OK)
     191                 :   , mIsPending(false)
     192                 :   , mDidOnStartRequest(false)
     193                 :   , mLastProgressUpdate(0)
     194                 :   , mRedirectCallback(nsnull)
     195              13 :   , mNewRedirectChannel(nsnull)
     196                 : {
     197              13 : }
     198                 : 
     199                 : nsresult
     200              10 : nsIncrementalDownload::FlushChunk()
     201                 : {
     202              10 :   NS_ASSERTION(mTotalSize != PRInt64(-1), "total size should be known");
     203                 : 
     204              10 :   if (mChunkLen == 0)
     205               5 :     return NS_OK;
     206                 : 
     207               5 :   nsresult rv = AppendToFile(mDest, mChunk, mChunkLen);
     208               5 :   if (NS_FAILED(rv))
     209               0 :     return rv;
     210                 : 
     211               5 :   mCurrentSize += PRInt64(mChunkLen);
     212               5 :   mChunkLen = 0;
     213                 : 
     214               5 :   return NS_OK;
     215                 : }
     216                 : 
     217                 : void
     218              10 : nsIncrementalDownload::UpdateProgress()
     219                 : {
     220              10 :   mLastProgressUpdate = PR_Now();
     221                 : 
     222              10 :   if (mProgressSink)
     223              10 :     mProgressSink->OnProgress(this, mObserverContext,
     224                 :                               PRUint64(PRInt64(mCurrentSize) + mChunkLen),
     225              10 :                               PRUint64(PRInt64(mTotalSize)));
     226              10 : }
     227                 : 
     228                 : nsresult
     229              18 : nsIncrementalDownload::CallOnStartRequest()
     230                 : {
     231              18 :   if (!mObserver || mDidOnStartRequest)
     232               5 :     return NS_OK;
     233                 : 
     234              13 :   mDidOnStartRequest = true;
     235              13 :   return mObserver->OnStartRequest(this, mObserverContext);
     236                 : }
     237                 : 
     238                 : void
     239              14 : nsIncrementalDownload::CallOnStopRequest()
     240                 : {
     241              14 :   if (!mObserver)
     242               1 :     return;
     243                 : 
     244                 :   // Ensure that OnStartRequest is always called once before OnStopRequest.
     245              13 :   nsresult rv = CallOnStartRequest();
     246              13 :   if (NS_SUCCEEDED(mStatus))
     247              10 :     mStatus = rv;
     248                 : 
     249              13 :   mIsPending = false;
     250                 : 
     251              13 :   mObserver->OnStopRequest(this, mObserverContext, mStatus);
     252              13 :   mObserver = nsnull;
     253              13 :   mObserverContext = nsnull;
     254                 : }
     255                 : 
     256                 : nsresult
     257              14 : nsIncrementalDownload::StartTimer(PRInt32 interval)
     258                 : {
     259                 :   nsresult rv;
     260              14 :   mTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
     261              14 :   if (NS_FAILED(rv))
     262               0 :     return rv;
     263                 : 
     264              14 :   return mTimer->Init(this, interval * 1000, nsITimer::TYPE_ONE_SHOT);
     265                 : }
     266                 : 
     267                 : nsresult
     268              13 : nsIncrementalDownload::ProcessTimeout()
     269                 : {
     270              13 :   NS_ASSERTION(!mChannel, "how can we have a channel?");
     271                 : 
     272                 :   // Handle existing error conditions
     273              13 :   if (NS_FAILED(mStatus)) {
     274               1 :     CallOnStopRequest();
     275               1 :     return NS_OK;
     276                 :   }
     277                 : 
     278                 :   // Fetch next chunk
     279                 :   
     280              24 :   nsCOMPtr<nsIChannel> channel;
     281              12 :   nsresult rv = NS_NewChannel(getter_AddRefs(channel), mFinalURI, nsnull,
     282              12 :                               nsnull, this, mLoadFlags);
     283              12 :   if (NS_FAILED(rv))
     284               0 :     return rv;
     285                 : 
     286              24 :   nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(channel, &rv);
     287              12 :   if (NS_FAILED(rv))
     288               0 :     return rv;
     289                 : 
     290              12 :   NS_ASSERTION(mCurrentSize != PRInt64(-1),
     291                 :       "we should know the current file size by now");
     292                 : 
     293              12 :   rv = ClearRequestHeader(http);
     294              12 :   if (NS_FAILED(rv))
     295               0 :     return rv;
     296                 : 
     297                 :   // Don't bother making a range request if we are just going to fetch the
     298                 :   // entire document.
     299              12 :   if (mInterval || mCurrentSize != PRInt64(0)) {
     300              12 :     nsCAutoString range;
     301               6 :     MakeRangeSpec(mCurrentSize, mTotalSize, mChunkSize, mInterval == 0, range);
     302                 : 
     303               6 :     rv = http->SetRequestHeader(NS_LITERAL_CSTRING("Range"), range, false);
     304               6 :     if (NS_FAILED(rv))
     305               0 :       return rv;
     306                 :   }
     307                 : 
     308              12 :   rv = channel->AsyncOpen(this, nsnull);
     309              12 :   if (NS_FAILED(rv))
     310               0 :     return rv;
     311                 : 
     312                 :   // Wait to assign mChannel when we know we are going to succeed.  This is
     313                 :   // important because we don't want to introduce a reference cycle between
     314                 :   // mChannel and this until we know for a fact that AsyncOpen has succeeded,
     315                 :   // thus ensuring that our stream listener methods will be invoked.
     316              12 :   mChannel = channel;
     317              12 :   return NS_OK;
     318                 : }
     319                 : 
     320                 : // Reads the current file size and validates it.
     321                 : nsresult
     322              13 : nsIncrementalDownload::ReadCurrentSize()
     323                 : {
     324                 :   PRInt64 size;
     325              13 :   nsresult rv = mDest->GetFileSize((PRInt64 *) &size);
     326              13 :   if (rv == NS_ERROR_FILE_NOT_FOUND ||
     327                 :       rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) {
     328               8 :     mCurrentSize = 0;
     329               8 :     return NS_OK;
     330                 :   }
     331               5 :   if (NS_FAILED(rv))
     332               0 :     return rv;
     333                 : 
     334               5 :   mCurrentSize = size; 
     335               5 :   return NS_OK;
     336                 : }
     337                 : 
     338                 : // nsISupports
     339                 : 
     340            1017 : NS_IMPL_ISUPPORTS9(nsIncrementalDownload,
     341                 :                    nsIIncrementalDownload,
     342                 :                    nsIRequest,
     343                 :                    nsIStreamListener,
     344                 :                    nsIRequestObserver,
     345                 :                    nsIObserver,
     346                 :                    nsIInterfaceRequestor,
     347                 :                    nsIChannelEventSink,
     348                 :                    nsISupportsWeakReference,
     349                 :                    nsIAsyncVerifyRedirectCallback)
     350                 : 
     351                 : // nsIRequest
     352                 : 
     353                 : NS_IMETHODIMP
     354               0 : nsIncrementalDownload::GetName(nsACString &name)
     355                 : {
     356               0 :   NS_ENSURE_TRUE(mURI, NS_ERROR_NOT_INITIALIZED);
     357                 : 
     358               0 :   return mURI->GetSpec(name);
     359                 : }
     360                 : 
     361                 : NS_IMETHODIMP
     362               0 : nsIncrementalDownload::IsPending(bool *isPending)
     363                 : {
     364               0 :   *isPending = mIsPending;
     365               0 :   return NS_OK;
     366                 : }
     367                 : 
     368                 : NS_IMETHODIMP
     369               0 : nsIncrementalDownload::GetStatus(nsresult *status)
     370                 : {
     371               0 :   *status = mStatus;
     372               0 :   return NS_OK;
     373                 : }
     374                 : 
     375                 : NS_IMETHODIMP
     376               2 : nsIncrementalDownload::Cancel(nsresult status)
     377                 : {
     378               2 :   NS_ENSURE_ARG(NS_FAILED(status));
     379                 : 
     380                 :   // Ignore this cancelation if we're already canceled.
     381               2 :   if (NS_FAILED(mStatus))
     382               1 :     return NS_OK;
     383                 : 
     384               1 :   mStatus = status;
     385                 : 
     386                 :   // Nothing more to do if callbacks aren't pending.
     387               1 :   if (!mIsPending)
     388               0 :     return NS_OK;
     389                 : 
     390               1 :   if (mChannel) {
     391               0 :     mChannel->Cancel(mStatus);
     392               0 :     NS_ASSERTION(!mTimer, "what is this timer object doing here?");
     393                 :   }
     394                 :   else {
     395                 :     // dispatch a timer callback event to drive invoking our listener's
     396                 :     // OnStopRequest.
     397               1 :     if (mTimer)
     398               1 :       mTimer->Cancel();
     399               1 :     StartTimer(0);
     400                 :   }
     401                 : 
     402               1 :   return NS_OK;
     403                 : }
     404                 : 
     405                 : NS_IMETHODIMP
     406               0 : nsIncrementalDownload::Suspend()
     407                 : {
     408               0 :   return NS_ERROR_NOT_IMPLEMENTED;
     409                 : }
     410                 : 
     411                 : NS_IMETHODIMP
     412               0 : nsIncrementalDownload::Resume()
     413                 : {
     414               0 :   return NS_ERROR_NOT_IMPLEMENTED;
     415                 : }
     416                 : 
     417                 : NS_IMETHODIMP
     418               0 : nsIncrementalDownload::GetLoadFlags(nsLoadFlags *loadFlags)
     419                 : {
     420               0 :   *loadFlags = mLoadFlags;
     421               0 :   return NS_OK;
     422                 : }
     423                 : 
     424                 : NS_IMETHODIMP
     425               0 : nsIncrementalDownload::SetLoadFlags(nsLoadFlags loadFlags)
     426                 : {
     427               0 :   mLoadFlags = loadFlags;
     428               0 :   return NS_OK;
     429                 : }
     430                 : 
     431                 : NS_IMETHODIMP
     432               0 : nsIncrementalDownload::GetLoadGroup(nsILoadGroup **loadGroup)
     433                 : {
     434               0 :   return NS_ERROR_NOT_IMPLEMENTED;
     435                 : }
     436                 : 
     437                 : NS_IMETHODIMP
     438               0 : nsIncrementalDownload::SetLoadGroup(nsILoadGroup *loadGroup)
     439                 : {
     440               0 :   return NS_ERROR_NOT_IMPLEMENTED;
     441                 : }
     442                 : 
     443                 : // nsIIncrementalDownload
     444                 : 
     445                 : NS_IMETHODIMP
     446              13 : nsIncrementalDownload::Init(nsIURI *uri, nsIFile *dest,
     447                 :                             PRInt32 chunkSize, PRInt32 interval)
     448                 : {
     449                 :   // Keep it simple: only allow initialization once
     450              13 :   NS_ENSURE_FALSE(mURI, NS_ERROR_ALREADY_INITIALIZED);
     451                 : 
     452              13 :   mDest = do_QueryInterface(dest);
     453              13 :   NS_ENSURE_ARG(mDest);
     454                 : 
     455              13 :   mURI = uri;
     456              13 :   mFinalURI = uri;
     457                 : 
     458              13 :   if (chunkSize > 0)
     459              13 :     mChunkSize = chunkSize;
     460              13 :   if (interval >= 0)
     461              13 :     mInterval = interval;
     462              13 :   return NS_OK;
     463                 : }
     464                 : 
     465                 : NS_IMETHODIMP
     466              26 : nsIncrementalDownload::GetURI(nsIURI **result)
     467                 : {
     468              26 :   NS_IF_ADDREF(*result = mURI);
     469              26 :   return NS_OK;
     470                 : }
     471                 : 
     472                 : NS_IMETHODIMP
     473              39 : nsIncrementalDownload::GetFinalURI(nsIURI **result)
     474                 : {
     475              39 :   NS_IF_ADDREF(*result = mFinalURI);
     476              39 :   return NS_OK;
     477                 : }
     478                 : 
     479                 : NS_IMETHODIMP
     480              10 : nsIncrementalDownload::GetDestination(nsIFile **result)
     481                 : {
     482              10 :   if (!mDest) {
     483               0 :     *result = nsnull;
     484               0 :     return NS_OK;
     485                 :   }
     486                 :   // Return a clone of mDest so that callers may modify the resulting nsIFile
     487                 :   // without corrupting our internal object.  This also works around the fact
     488                 :   // that some nsIFile impls may cache the result of stat'ing the filesystem.
     489              10 :   return mDest->Clone(result);
     490                 : }
     491                 : 
     492                 : NS_IMETHODIMP
     493               0 : nsIncrementalDownload::GetTotalSize(PRInt64 *result)
     494                 : {
     495               0 :   *result = mTotalSize;
     496               0 :   return NS_OK;
     497                 : }
     498                 : 
     499                 : NS_IMETHODIMP
     500               0 : nsIncrementalDownload::GetCurrentSize(PRInt64 *result)
     501                 : {
     502               0 :   *result = mCurrentSize;
     503               0 :   return NS_OK;
     504                 : }
     505                 : 
     506                 : NS_IMETHODIMP
     507              13 : nsIncrementalDownload::Start(nsIRequestObserver *observer,
     508                 :                              nsISupports *context)
     509                 : {
     510              13 :   NS_ENSURE_ARG(observer);
     511              13 :   NS_ENSURE_FALSE(mIsPending, NS_ERROR_IN_PROGRESS);
     512                 : 
     513                 :   // Observe system shutdown so we can be sure to release any reference held
     514                 :   // between ourselves and the timer.  We have the observer service hold a weak
     515                 :   // reference to us, so that we don't have to worry about calling
     516                 :   // RemoveObserver.  XXX(darin): The timer code should do this for us.
     517              26 :   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
     518              13 :   if (obs)
     519              13 :     obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true);
     520                 : 
     521              13 :   nsresult rv = ReadCurrentSize();
     522              13 :   if (NS_FAILED(rv))
     523               0 :     return rv;
     524                 : 
     525              13 :   rv = StartTimer(0);
     526              13 :   if (NS_FAILED(rv))
     527               0 :     return rv;
     528                 : 
     529              13 :   mObserver = observer;
     530              13 :   mObserverContext = context;
     531              13 :   mProgressSink = do_QueryInterface(observer);  // ok if null
     532                 : 
     533              13 :   mIsPending = true;
     534              13 :   return NS_OK;
     535                 : }
     536                 : 
     537                 : // nsIRequestObserver
     538                 : 
     539                 : NS_IMETHODIMP
     540              12 : nsIncrementalDownload::OnStartRequest(nsIRequest *request,
     541                 :                                       nsISupports *context)
     542                 : {
     543                 :   nsresult rv;
     544                 : 
     545              24 :   nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(request, &rv);
     546              12 :   if (NS_FAILED(rv))
     547               0 :     return rv;
     548                 : 
     549                 :   // Ensure that we are receiving a 206 response.
     550                 :   PRUint32 code;
     551              12 :   rv = http->GetResponseStatus(&code);
     552              12 :   if (NS_FAILED(rv))
     553               1 :     return rv;
     554              11 :   if (code != 206) {
     555                 :     // We may already have the entire file downloaded, in which case
     556                 :     // our request for a range beyond the end of the file would have
     557                 :     // been met with an error response code.
     558              11 :     if (code == 416 && mTotalSize == PRInt64(-1)) {
     559               5 :       mTotalSize = mCurrentSize;
     560                 :       // Return an error code here to suppress OnDataAvailable.
     561               5 :       return NS_ERROR_DOWNLOAD_COMPLETE;
     562                 :     }
     563                 :     // The server may have decided to give us all of the data in one chunk.  If
     564                 :     // we requested a partial range, then we don't want to download all of the
     565                 :     // data at once.  So, we'll just try again, but if this keeps happening then
     566                 :     // we'll eventually give up.
     567               6 :     if (code == 200) {
     568               5 :       if (mInterval) {
     569               0 :         mChannel = nsnull;
     570               0 :         if (++mNonPartialCount > MAX_RETRY_COUNT) {
     571               0 :           NS_WARNING("unable to fetch a byte range; giving up");
     572               0 :           return NS_ERROR_FAILURE;
     573                 :         }
     574                 :         // Increase delay with each failure.
     575               0 :         StartTimer(mInterval * mNonPartialCount);
     576               0 :         return NS_ERROR_DOWNLOAD_NOT_PARTIAL;
     577                 :       }
     578                 :       // Since we have been asked to download the rest of the file, we can deal
     579                 :       // with a 200 response.  This may result in downloading the beginning of
     580                 :       // the file again, but that can't really be helped.
     581                 :     } else {
     582               1 :       NS_WARNING("server response was unexpected");
     583               1 :       return NS_ERROR_UNEXPECTED;
     584                 :     }
     585                 :   } else {
     586                 :     // We got a partial response, so clear this counter in case the next chunk
     587                 :     // results in a 200 response.
     588               0 :     mNonPartialCount = 0;
     589                 :   }
     590                 : 
     591                 :   // Do special processing after the first response.
     592               5 :   if (mTotalSize == PRInt64(-1)) {
     593                 :     // Update knowledge of mFinalURI
     594               5 :     rv = http->GetURI(getter_AddRefs(mFinalURI));
     595               5 :     if (NS_FAILED(rv))
     596               0 :       return rv;
     597                 : 
     598               5 :     if (code == 206) {
     599                 :       // OK, read the Content-Range header to determine the total size of this
     600                 :       // download file.
     601               0 :       nsCAutoString buf;
     602               0 :       rv = http->GetResponseHeader(NS_LITERAL_CSTRING("Content-Range"), buf);
     603               0 :       if (NS_FAILED(rv))
     604               0 :         return rv;
     605               0 :       PRInt32 slash = buf.FindChar('/');
     606               0 :       if (slash == kNotFound) {
     607               0 :         NS_WARNING("server returned invalid Content-Range header!");
     608               0 :         return NS_ERROR_UNEXPECTED;
     609                 :       }
     610               0 :       if (PR_sscanf(buf.get() + slash + 1, "%lld", (PRInt64 *) &mTotalSize) != 1)
     611               0 :         return NS_ERROR_UNEXPECTED;
     612                 :     } else {
     613                 :       // Use nsIPropertyBag2 to fetch the content length as it exposes the
     614                 :       // value as a 64-bit number.
     615              10 :       nsCOMPtr<nsIPropertyBag2> props = do_QueryInterface(request, &rv);
     616               5 :       if (NS_FAILED(rv))
     617               0 :         return rv;
     618               5 :       rv = props->GetPropertyAsInt64(NS_CHANNEL_PROP_CONTENT_LENGTH,
     619               5 :                                      &mTotalSize);
     620                 :       // We need to know the total size of the thing we're trying to download.
     621               5 :       if (mTotalSize == PRInt64(-1)) {
     622               0 :         NS_WARNING("server returned no content-length header!");
     623               0 :         return NS_ERROR_UNEXPECTED;
     624                 :       }
     625                 :       // Need to truncate (or create, if it doesn't exist) the file since we
     626                 :       // are downloading the whole thing.
     627               5 :       WriteToFile(mDest, nsnull, 0, PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE);
     628              10 :       mCurrentSize = 0;
     629                 :     }
     630                 : 
     631                 :     // Notify observer that we are starting...
     632               5 :     rv = CallOnStartRequest();
     633               5 :     if (NS_FAILED(rv))
     634               0 :       return rv;
     635                 :   }
     636                 : 
     637                 :   // Adjust mChunkSize accordingly if mCurrentSize is close to mTotalSize.
     638               5 :   PRInt64 diff = mTotalSize - mCurrentSize;
     639               5 :   if (diff <= PRInt64(0)) {
     640               0 :     NS_WARNING("about to set a bogus chunk size; giving up");
     641               0 :     return NS_ERROR_UNEXPECTED;
     642                 :   }
     643                 : 
     644               5 :   if (diff < PRInt64(mChunkSize))
     645               5 :     mChunkSize = PRUint32(diff);
     646                 : 
     647              10 :   mChunk = new char[mChunkSize];
     648               5 :   if (!mChunk)
     649               0 :     rv = NS_ERROR_OUT_OF_MEMORY;
     650                 : 
     651               5 :   return rv;
     652                 : }
     653                 : 
     654                 : NS_IMETHODIMP
     655              12 : nsIncrementalDownload::OnStopRequest(nsIRequest *request,
     656                 :                                      nsISupports *context,
     657                 :                                      nsresult status)
     658                 : {
     659                 :   // Not a real error; just a trick to kill off the channel without our
     660                 :   // listener having to care.
     661              12 :   if (status == NS_ERROR_DOWNLOAD_NOT_PARTIAL)
     662               0 :     return NS_OK;
     663                 : 
     664                 :   // Not a real error; just a trick used to suppress OnDataAvailable calls.
     665              12 :   if (status == NS_ERROR_DOWNLOAD_COMPLETE)
     666               5 :     status = NS_OK;
     667                 : 
     668              12 :   if (NS_SUCCEEDED(mStatus))
     669              12 :     mStatus = status;
     670                 : 
     671              12 :   if (mChunk) {
     672               5 :     if (NS_SUCCEEDED(mStatus))
     673               5 :       mStatus = FlushChunk();
     674                 : 
     675               5 :     mChunk = nsnull;  // deletes memory
     676               5 :     mChunkLen = 0;
     677               5 :     UpdateProgress();
     678                 :   }
     679                 : 
     680              12 :   mChannel = nsnull;
     681                 : 
     682                 :   // Notify listener if we hit an error or finished
     683              12 :   if (NS_FAILED(mStatus) || mCurrentSize == mTotalSize) {
     684              12 :     CallOnStopRequest();
     685              12 :     return NS_OK;
     686                 :   }
     687                 : 
     688               0 :   return StartTimer(mInterval);  // Do next chunk
     689                 : }
     690                 : 
     691                 : // nsIStreamListener
     692                 : 
     693                 : NS_IMETHODIMP
     694               5 : nsIncrementalDownload::OnDataAvailable(nsIRequest *request,
     695                 :                                        nsISupports *context,
     696                 :                                        nsIInputStream *input,
     697                 :                                        PRUint32 offset,
     698                 :                                        PRUint32 count)
     699                 : {
     700              15 :   while (count) {
     701               5 :     PRUint32 space = mChunkSize - mChunkLen;
     702               5 :     PRUint32 n, len = NS_MIN(space, count);
     703                 : 
     704               5 :     nsresult rv = input->Read(mChunk + mChunkLen, len, &n);
     705               5 :     if (NS_FAILED(rv))
     706               0 :       return rv;
     707               5 :     if (n != len)
     708               0 :       return NS_ERROR_UNEXPECTED;
     709                 : 
     710               5 :     count -= n;
     711               5 :     mChunkLen += n;
     712                 : 
     713               5 :     if (mChunkLen == mChunkSize)
     714               5 :       FlushChunk();
     715                 :   }
     716                 : 
     717               5 :   if (PR_Now() > mLastProgressUpdate + UPDATE_PROGRESS_INTERVAL)
     718               5 :     UpdateProgress();
     719                 : 
     720               5 :   return NS_OK;
     721                 : }
     722                 : 
     723                 : // nsIObserver
     724                 : 
     725                 : NS_IMETHODIMP
     726              14 : nsIncrementalDownload::Observe(nsISupports *subject, const char *topic,
     727                 :                                const PRUnichar *data)
     728                 : {
     729              14 :   if (strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
     730               1 :     Cancel(NS_ERROR_ABORT);
     731                 : 
     732                 :     // Since the app is shutting down, we need to go ahead and notify our
     733                 :     // observer here.  Otherwise, we would notify them after XPCOM has been
     734                 :     // shutdown or not at all.
     735               1 :     CallOnStopRequest();
     736                 :   }
     737              13 :   else if (strcmp(topic, NS_TIMER_CALLBACK_TOPIC) == 0) {
     738              13 :     mTimer = nsnull;
     739              13 :     nsresult rv = ProcessTimeout();
     740              13 :     if (NS_FAILED(rv))
     741               0 :       Cancel(rv);
     742                 :   }
     743              14 :   return NS_OK;
     744                 : }
     745                 : 
     746                 : // nsIInterfaceRequestor
     747                 : 
     748                 : NS_IMETHODIMP
     749              20 : nsIncrementalDownload::GetInterface(const nsIID &iid, void **result)
     750                 : {
     751              20 :   if (iid.Equals(NS_GET_IID(nsIChannelEventSink))) {
     752               0 :     NS_ADDREF_THIS();
     753               0 :     *result = static_cast<nsIChannelEventSink *>(this);
     754               0 :     return NS_OK;
     755                 :   }
     756                 : 
     757              40 :   nsCOMPtr<nsIInterfaceRequestor> ir = do_QueryInterface(mObserver);
     758              20 :   if (ir)
     759              20 :     return ir->GetInterface(iid, result);
     760                 : 
     761               0 :   return NS_ERROR_NO_INTERFACE;
     762                 : }
     763                 : 
     764                 : nsresult 
     765              12 : nsIncrementalDownload::ClearRequestHeader(nsIHttpChannel *channel)
     766                 : {
     767              12 :   NS_ENSURE_ARG(channel);
     768                 :   
     769                 :   // We don't support encodings -- they make the Content-Length not equal
     770                 :   // to the actual size of the data. 
     771              12 :   return channel->SetRequestHeader(NS_LITERAL_CSTRING("Accept-Encoding"),
     772              24 :                                    NS_LITERAL_CSTRING(""), false);
     773                 : }
     774                 : 
     775                 : // nsIChannelEventSink
     776                 : 
     777                 : NS_IMETHODIMP
     778               0 : nsIncrementalDownload::AsyncOnChannelRedirect(nsIChannel *oldChannel,
     779                 :                                               nsIChannel *newChannel,
     780                 :                                               PRUint32 flags,
     781                 :                                               nsIAsyncVerifyRedirectCallback *cb)
     782                 : {
     783                 :   // In response to a redirect, we need to propagate the Range header.  See bug
     784                 :   // 311595.  Any failure code returned from this function aborts the redirect.
     785                 :  
     786               0 :   nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(oldChannel);
     787               0 :   NS_ENSURE_STATE(http);
     788                 : 
     789               0 :   nsCOMPtr<nsIHttpChannel> newHttpChannel = do_QueryInterface(newChannel);
     790               0 :   NS_ENSURE_STATE(newHttpChannel);
     791                 : 
     792               0 :   NS_NAMED_LITERAL_CSTRING(rangeHdr, "Range");
     793                 : 
     794               0 :   nsresult rv = ClearRequestHeader(newHttpChannel);
     795               0 :   if (NS_FAILED(rv))
     796               0 :     return rv;
     797                 : 
     798                 :   // If we didn't have a Range header, then we must be doing a full download.
     799               0 :   nsCAutoString rangeVal;
     800               0 :   http->GetRequestHeader(rangeHdr, rangeVal);
     801               0 :   if (!rangeVal.IsEmpty()) {
     802               0 :     rv = newHttpChannel->SetRequestHeader(rangeHdr, rangeVal, false);
     803               0 :     NS_ENSURE_SUCCESS(rv, rv);
     804                 :   }
     805                 : 
     806                 :   // Prepare to receive callback
     807               0 :   mRedirectCallback = cb;
     808               0 :   mNewRedirectChannel = newChannel;
     809                 : 
     810                 :   // Give the observer a chance to see this redirect notification.
     811               0 :   nsCOMPtr<nsIChannelEventSink> sink = do_GetInterface(mObserver);
     812               0 :   if (sink) {
     813               0 :     rv = sink->AsyncOnChannelRedirect(oldChannel, newChannel, flags, this);
     814               0 :     if (NS_FAILED(rv)) {
     815               0 :         mRedirectCallback = nsnull;
     816               0 :         mNewRedirectChannel = nsnull;
     817                 :     }
     818               0 :     return rv;
     819                 :   }
     820               0 :   (void) OnRedirectVerifyCallback(NS_OK);
     821               0 :   return NS_OK;
     822                 : }
     823                 : 
     824                 : NS_IMETHODIMP
     825               0 : nsIncrementalDownload::OnRedirectVerifyCallback(nsresult result)
     826                 : {
     827               0 :   NS_ASSERTION(mRedirectCallback, "mRedirectCallback not set in callback");
     828               0 :   NS_ASSERTION(mNewRedirectChannel, "mNewRedirectChannel not set in callback");
     829                 : 
     830                 :   // Update mChannel, so we can Cancel the new channel.
     831               0 :   if (NS_SUCCEEDED(result))
     832               0 :     mChannel = mNewRedirectChannel;
     833                 : 
     834               0 :   mRedirectCallback->OnRedirectVerifyCallback(result);
     835               0 :   mRedirectCallback = nsnull;
     836               0 :   mNewRedirectChannel = nsnull;
     837               0 :   return NS_OK;
     838                 : }
     839                 : 
     840                 : extern nsresult
     841              13 : net_NewIncrementalDownload(nsISupports *outer, const nsIID &iid, void **result)
     842                 : {
     843              13 :   if (outer)
     844               0 :     return NS_ERROR_NO_AGGREGATION;
     845                 : 
     846              13 :   nsIncrementalDownload *d = new nsIncrementalDownload();
     847              13 :   if (!d)
     848               0 :     return NS_ERROR_OUT_OF_MEMORY;
     849                 :   
     850              13 :   NS_ADDREF(d);
     851              13 :   nsresult rv = d->QueryInterface(iid, result);
     852              13 :   NS_RELEASE(d);
     853              13 :   return rv;
     854                 : }

Generated by: LCOV version 1.7