diff --git a/mozilla/netwerk/base/public/Makefile.in b/mozilla/netwerk/base/public/Makefile.in index 8e247324ec6..7b9bad62c2d 100644 --- a/mozilla/netwerk/base/public/Makefile.in +++ b/mozilla/netwerk/base/public/Makefile.in @@ -69,6 +69,7 @@ XPIDLSRCS = \ nsIDownloader.idl \ nsIEncodedChannel.idl \ nsIFileStreams.idl \ + nsIIncrementalDownload.idl \ nsIInputStreamPump.idl \ nsIInputStreamChannel.idl \ nsIMIMEInputStream.idl \ diff --git a/mozilla/netwerk/base/public/nsIIncrementalDownload.idl b/mozilla/netwerk/base/public/nsIIncrementalDownload.idl new file mode 100644 index 00000000000..0dc9933612b --- /dev/null +++ b/mozilla/netwerk/base/public/nsIIncrementalDownload.idl @@ -0,0 +1,137 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is mozilla.org code. + * + * The Initial Developer of the Original Code is Google Inc. + * Portions created by the Initial Developer are Copyright (C) 2005 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Darin Fisher + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "nsIRequest.idl" + +interface nsIURI; +interface nsIFile; +interface nsIRequestObserver; + +/** + * An incremental download object attempts to fetch a file piecemeal over time + * in an effort to minimize network bandwidth usage. + * + * Canceling a background download does not cause the file on disk to be + * deleted. + */ +[scriptable, uuid(6687823f-56c4-461d-93a1-7f6cb7dfbfba)] +interface nsIIncrementalDownload : nsIRequest +{ + /** + * Initialize the incremental download object. If the destination file + * already exists, then only the remaining portion of the file will be + * fetched. + * + * NOTE: The downloader will create the destination file if it does not + * already exist. It will create the file with the permissions 0600 if + * needed. To affect the permissions of the file, consumers of this + * interface may create an empty file at the specified destination prior to + * starting the incremental download. + * + * NOTE: Since this class may create a temporary file at the specified + * destination, it is advisable for the consumer of this interface to specify + * a file name for the destination that would not tempt the user into + * double-clicking it. For example, it might be wise to append a file + * extension like ".part" to the end of the destination to protect users from + * accidentally running "blah.exe" before it is a complete file. + * + * @param uri + * The URI to fetch. + * @param destination + * The location where the file is to be stored. + * @param chunkSize + * The size of the chunks to fetch. + * @param intervalInSeconds + * The amount of time to wait between fetching chunks. Pass -1 to use + * the default interval, or 0 to fetch the remaining part of the file + * in one chunk. + */ + void init(in nsIURI uri, in nsIFile destination, in long chunkSize, + in long intervalInSeconds); + + /** + * The URI being fetched. + */ + readonly attribute nsIURI URI; + + /** + * The URI being fetched after any redirects have been followed. This + * attribute is set just prior to calling OnStartRequest on the observer + * passed to the start method. + */ + readonly attribute nsIURI finalURI; + + /** + * The file where the download is being written. + */ + readonly attribute nsIFile destination; + + /** + * The total number of bytes for the requested file. This attribute is set + * just prior to calling OnStartRequest on the observer passed to the start + * method. + * + * This attribute has a value of -1 if the total size is unknown. + */ + readonly attribute long long totalSize; + + /** + * The current number of bytes downloaded so far. This attribute is set just + * prior to calling OnStartRequest on the observer passed to the start + * method. + * + * This attribute has a value of -1 if the current size is unknown. + */ + readonly attribute long long currentSize; + + /** + * Start the incremental download. + * + * @param observer + * An observer to be notified of various events. OnStartRequest is + * called when finalURI and totalSize have been determined or when an + * error occurs. OnStopRequest is called when the file is completely + * downloaded or when an error occurs. If this object also implements + * nsIProgressEventSink, then its OnProgress method will be called as + * data is written to the destination file. + * @param ctxt + * User defined object forwarded to the observer's methods. + */ + void start(in nsIRequestObserver observer, + in nsISupports ctxt); +}; diff --git a/mozilla/netwerk/base/src/Makefile.in b/mozilla/netwerk/base/src/Makefile.in index f469b30540d..b6f063ded14 100644 --- a/mozilla/netwerk/base/src/Makefile.in +++ b/mozilla/netwerk/base/src/Makefile.in @@ -64,6 +64,7 @@ CPPSRCS = \ nsDirectoryIndexStream.cpp \ nsDownloader.cpp \ nsFileStreams.cpp \ + nsIncrementalDownload.cpp \ nsInputStreamChannel.cpp \ nsInputStreamPump.cpp \ nsStreamTransportService.cpp \ diff --git a/mozilla/netwerk/base/src/nsIncrementalDownload.cpp b/mozilla/netwerk/base/src/nsIncrementalDownload.cpp new file mode 100644 index 00000000000..585fd60d526 --- /dev/null +++ b/mozilla/netwerk/base/src/nsIncrementalDownload.cpp @@ -0,0 +1,644 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is mozilla.org code. + * + * The Initial Developer of the Original Code is Google Inc. + * Portions created by the Initial Developer are Copyright (C) 2005 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Darin Fisher + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "nsIIncrementalDownload.h" +#include "nsIRequestObserver.h" +#include "nsIProgressEventSink.h" +#include "nsIObserverService.h" +#include "nsIObserver.h" +#include "nsIServiceManager.h" +#include "nsILocalFile.h" +#include "nsITimer.h" +#include "nsInt64.h" +#include "nsNetUtil.h" +#include "nsAutoPtr.h" +#include "nsWeakReference.h" +#include "prio.h" +#include "prprf.h" + +// Error code used internally by the incremental downloader to cancel the +// network channel when the download is already complete. +#define NS_ERROR_DOWNLOAD_COMPLETE \ + NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_GENERAL, 1) + +// Default values used to initialize a nsIncrementalDownload object. +#define DEFAULT_CHUNK_SIZE (4096 * 16) // bytes +#define DEFAULT_INTERVAL 60 // seconds + +//----------------------------------------------------------------------------- + +static nsresult +WriteToFile(nsILocalFile *lf, const char *data, PRUint32 len, PRInt32 flags) +{ + PRFileDesc *fd; + nsresult rv = lf->OpenNSPRFileDesc(flags, 0600, &fd); + if (NS_FAILED(rv)) + return rv; + + rv = PR_Write(fd, data, len) == len ? NS_OK : NS_ERROR_FAILURE; + + PR_Close(fd); + return rv; +} + +static nsresult +AppendToFile(nsILocalFile *lf, const char *data, PRUint32 len) +{ + PRInt32 flags = PR_WRONLY | PR_CREATE_FILE | PR_APPEND; + return WriteToFile(lf, data, len, flags); +} + +// maxSize may be -1 if unknown +static void +MakeRangeSpec(const nsInt64 &size, const nsInt64 &maxSize, PRInt32 chunkSize, + PRBool fetchRemaining, nsCString &rangeSpec) +{ + rangeSpec.AssignLiteral("bytes="); + rangeSpec.AppendInt(PRInt64(size)); + rangeSpec.Append('-'); + + if (fetchRemaining) + return; + + nsInt64 end = size + nsInt64(chunkSize); + if (maxSize != nsInt64(-1) && end > maxSize) + end = maxSize; + end -= 1; + + rangeSpec.AppendInt(PRInt64(end)); +} + +//----------------------------------------------------------------------------- + +class nsIncrementalDownload : public nsIIncrementalDownload + , public nsIStreamListener + , public nsIObserver + , public nsSupportsWeakReference +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIREQUEST + NS_DECL_NSIINCREMENTALDOWNLOAD + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + NS_DECL_NSIOBSERVER + + nsIncrementalDownload(); + +private: + ~nsIncrementalDownload() {} + nsresult FlushChunk(); + nsresult CallOnStartRequest(); + void CallOnStopRequest(); + nsresult StartTimer(PRInt32 interval); + nsresult ProcessTimeout(); + nsresult ReadCurrentSize(); + + nsCOMPtr mObserver; + nsCOMPtr mObserverContext; + nsCOMPtr mProgressSink; + nsCOMPtr mURI; + nsCOMPtr mFinalURI; + nsCOMPtr mDest; + nsCOMPtr mChannel; + nsCOMPtr mTimer; + nsAutoArrayPtr mChunk; + PRInt32 mChunkLen; + PRInt32 mChunkSize; + PRInt32 mInterval; + nsInt64 mTotalSize; + nsInt64 mCurrentSize; + PRUint32 mLoadFlags; + nsresult mStatus; + PRPackedBool mIsPending; + PRPackedBool mDidOnStartRequest; +}; + +nsIncrementalDownload::nsIncrementalDownload() + : mChunkLen(0) + , mChunkSize(DEFAULT_CHUNK_SIZE) + , mInterval(DEFAULT_INTERVAL) + , mTotalSize(-1) + , mCurrentSize(-1) + , mLoadFlags(LOAD_NORMAL) + , mStatus(NS_OK) + , mIsPending(PR_FALSE) + , mDidOnStartRequest(PR_FALSE) +{ +} + +nsresult +nsIncrementalDownload::FlushChunk() +{ + NS_ASSERTION(mTotalSize != nsInt64(-1), "total size should be known"); + + if (mChunkLen == 0) + return NS_OK; + + nsresult rv = AppendToFile(mDest, mChunk, mChunkLen); + if (NS_FAILED(rv)) + return rv; + + mCurrentSize += nsInt64(mChunkLen); + mChunkLen = 0; + + if (mProgressSink) + mProgressSink->OnProgress(this, mObserverContext, + PRUint64(PRInt64(mCurrentSize)), + PRUint64(PRInt64(mTotalSize))); + return NS_OK; +} + +nsresult +nsIncrementalDownload::CallOnStartRequest() +{ + if (!mObserver || mDidOnStartRequest) + return NS_OK; + + mDidOnStartRequest = PR_TRUE; + return mObserver->OnStartRequest(this, mObserverContext); +} + +void +nsIncrementalDownload::CallOnStopRequest() +{ + if (!mObserver) + return; + + // Ensure that OnStartRequest is always called once before OnStopRequest. + nsresult rv = CallOnStartRequest(); + if (NS_SUCCEEDED(mStatus)) + mStatus = rv; + + mIsPending = PR_FALSE; + + mObserver->OnStopRequest(this, mObserverContext, mStatus); + mObserver = nsnull; + mObserverContext = nsnull; +} + +nsresult +nsIncrementalDownload::StartTimer(PRInt32 interval) +{ + nsresult rv; + mTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv); + if (NS_FAILED(rv)) + return rv; + + return mTimer->Init(this, interval * 1000, nsITimer::TYPE_ONE_SHOT); +} + +nsresult +nsIncrementalDownload::ProcessTimeout() +{ + NS_ASSERTION(!mChannel, "how can we have a channel?"); + + // Handle existing error conditions + if (NS_FAILED(mStatus)) { + CallOnStopRequest(); + return NS_OK; + } + + // Fetch next chunk + + nsCOMPtr channel; + nsresult rv = NS_NewChannel(getter_AddRefs(channel), mFinalURI); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr http = do_QueryInterface(channel, &rv); + if (NS_FAILED(rv)) + return rv; + + NS_ASSERTION(mCurrentSize != nsInt64(-1), + "we should know the current file size by now"); + + nsCAutoString range; + MakeRangeSpec(mCurrentSize, mTotalSize, mChunkSize, mInterval == 0, range); + + rv = http->SetRequestHeader(NS_LITERAL_CSTRING("Range"), range, PR_FALSE); + if (NS_FAILED(rv)) + return rv; + + rv = channel->AsyncOpen(this, nsnull); + if (NS_FAILED(rv)) + return rv; + + // Wait to assign mChannel when we know we are going to succeed. This is + // important because we don't want to introduce a reference cycle between + // mChannel and this until we know for a fact that AsyncOpen has succeeded, + // thus ensuring that our stream listener methods will be invoked. + mChannel = channel; + return NS_OK; +} + +// Reads the current file size and validates it. +nsresult +nsIncrementalDownload::ReadCurrentSize() +{ + nsInt64 size; + nsresult rv = mDest->GetFileSize((PRInt64 *) &size); + if (rv == NS_ERROR_FILE_NOT_FOUND) { + mCurrentSize = 0; + return NS_OK; + } + if (NS_FAILED(rv)) + return rv; + + mCurrentSize = size; + return NS_OK; +} + +// nsISupports + +NS_IMPL_ISUPPORTS6(nsIncrementalDownload, + nsIIncrementalDownload, + nsIRequest, + nsIStreamListener, + nsIRequestObserver, + nsIObserver, + nsISupportsWeakReference) + +// nsIRequest + +NS_IMETHODIMP +nsIncrementalDownload::GetName(nsACString &name) +{ + NS_ENSURE_TRUE(mURI, NS_ERROR_NOT_INITIALIZED); + + return mURI->GetSpec(name); +} + +NS_IMETHODIMP +nsIncrementalDownload::IsPending(PRBool *isPending) +{ + *isPending = mIsPending; + return NS_OK; +} + +NS_IMETHODIMP +nsIncrementalDownload::GetStatus(nsresult *status) +{ + *status = mStatus; + return NS_OK; +} + +NS_IMETHODIMP +nsIncrementalDownload::Cancel(nsresult status) +{ + NS_ENSURE_ARG(NS_FAILED(status)); + + // Ignore this cancelation if we're already canceled. + if (NS_FAILED(mStatus)) + return NS_OK; + + mStatus = status; + + // Nothing more to do if callbacks aren't pending. + if (!mIsPending) + return NS_OK; + + if (mChannel) { + mChannel->Cancel(mStatus); + NS_ASSERTION(!mTimer, "what is this timer object doing here?"); + } + else { + // dispatch a timer callback event to drive invoking our listener's + // OnStopRequest. + if (mTimer) + mTimer->Cancel(); + StartTimer(0); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsIncrementalDownload::Suspend() +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsIncrementalDownload::Resume() +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsIncrementalDownload::GetLoadFlags(nsLoadFlags *loadFlags) +{ + *loadFlags = mLoadFlags; + return NS_OK; +} + +NS_IMETHODIMP +nsIncrementalDownload::SetLoadFlags(nsLoadFlags loadFlags) +{ + mLoadFlags = loadFlags; + return NS_OK; +} + +NS_IMETHODIMP +nsIncrementalDownload::GetLoadGroup(nsILoadGroup **loadGroup) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsIncrementalDownload::SetLoadGroup(nsILoadGroup *loadGroup) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +// nsIIncrementalDownload + +NS_IMETHODIMP +nsIncrementalDownload::Init(nsIURI *uri, nsIFile *dest, + PRInt32 chunkSize, PRInt32 interval) +{ + // Keep it simple: only allow initialization once + NS_ENSURE_FALSE(mURI, NS_ERROR_ALREADY_INITIALIZED); + + mDest = do_QueryInterface(dest); + NS_ENSURE_ARG(mDest); + + mURI = uri; + mFinalURI = uri; + + if (chunkSize != -1) + mChunkSize = chunkSize; + if (interval != -1) + mInterval = interval; + return NS_OK; +} + +NS_IMETHODIMP +nsIncrementalDownload::GetURI(nsIURI **result) +{ + NS_IF_ADDREF(*result = mURI); + return NS_OK; +} + +NS_IMETHODIMP +nsIncrementalDownload::GetFinalURI(nsIURI **result) +{ + NS_IF_ADDREF(*result = mFinalURI); + return NS_OK; +} + +NS_IMETHODIMP +nsIncrementalDownload::GetDestination(nsIFile **result) +{ + NS_IF_ADDREF(*result = mDest); + return NS_OK; +} + +NS_IMETHODIMP +nsIncrementalDownload::GetTotalSize(PRInt64 *result) +{ + *result = mTotalSize; + return NS_OK; +} + +NS_IMETHODIMP +nsIncrementalDownload::GetCurrentSize(PRInt64 *result) +{ + *result = mCurrentSize; + return NS_OK; +} + +NS_IMETHODIMP +nsIncrementalDownload::Start(nsIRequestObserver *observer, + nsISupports *context) +{ + NS_ENSURE_ARG(observer); + NS_ENSURE_FALSE(mIsPending, NS_ERROR_IN_PROGRESS); + + // Observe system shutdown so we can be sure to release any reference held + // between ourselves and the timer. We have the observer service hold a weak + // reference to us, so that we don't have to worry about calling + // RemoveObserver. XXX(darin): The timer code should do this for us. + nsCOMPtr obs = + do_GetService("@mozilla.org/observer-service;1"); + if (obs) + obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, PR_TRUE); + + nsresult rv = ReadCurrentSize(); + if (NS_FAILED(rv)) + return rv; + + rv = StartTimer(0); + if (NS_FAILED(rv)) + return rv; + + mObserver = observer; + mObserverContext = context; + mProgressSink = do_QueryInterface(observer); // ok if null + + mIsPending = PR_TRUE; + return NS_OK; +} + +// nsIRequestObserver + +NS_IMETHODIMP +nsIncrementalDownload::OnStartRequest(nsIRequest *request, + nsISupports *context) +{ + nsresult rv; + + nsCOMPtr http = do_QueryInterface(request, &rv); + if (NS_FAILED(rv)) + return rv; + + // Ensure that we are receiving a 206 response. + PRUint32 code; + rv = http->GetResponseStatus(&code); + if (NS_FAILED(rv)) + return rv; + if (code != 206) { + // We may already have the entire file downloaded, in which case + // our request for a range beyond the end of the file would have + // been met with an error response code. + if (code == 416 && mTotalSize == nsInt64(-1)) { + mTotalSize = mCurrentSize; + // Return an error code here to suppress OnDataAvailable. + return NS_ERROR_DOWNLOAD_COMPLETE; + } + NS_WARNING("server response was unexpected"); + return NS_ERROR_UNEXPECTED; + } + + // Do special processing after the first response. + if (mTotalSize == nsInt64(-1)) { + // Update knowledge of mFinalURI + rv = http->GetURI(getter_AddRefs(mFinalURI)); + if (NS_FAILED(rv)) + return rv; + + // OK, read the Content-Range header to determine the total size of this + // download file. + nsCAutoString buf; + rv = http->GetResponseHeader(NS_LITERAL_CSTRING("Content-Range"), buf); + if (NS_FAILED(rv)) + return rv; + PRInt32 slash = buf.FindChar('/'); + if (slash == kNotFound) { + NS_WARNING("server returned invalid Content-Range header!"); + return NS_ERROR_UNEXPECTED; + } + if (PR_sscanf(buf.get() + slash + 1, "%lld", (PRInt64 *) &mTotalSize) != 1) + return NS_ERROR_UNEXPECTED; + + // Notify observer that we are starting... + rv = CallOnStartRequest(); + if (NS_FAILED(rv)) + return rv; + } + + // Adjust mChunkSize accordingly if mCurrentSize is close to mTotalSize. + nsInt64 diff = mTotalSize - mCurrentSize; + if (diff < nsInt64(mChunkSize)) + mChunkSize = PRUint32(diff); + + mChunk = new char[mChunkSize]; + if (!mChunk) + rv = NS_ERROR_OUT_OF_MEMORY; + + return rv; +} + +NS_IMETHODIMP +nsIncrementalDownload::OnStopRequest(nsIRequest *request, + nsISupports *context, + nsresult status) +{ + // Not a real error; just a trick used to suppress OnDataAvailable calls. + if (status == NS_ERROR_DOWNLOAD_COMPLETE) + status = NS_OK; + + if (NS_SUCCEEDED(mStatus)) + mStatus = status; + + if (mChunk) { + if (NS_SUCCEEDED(mStatus)) + mStatus = FlushChunk(); + + mChunk = nsnull; // deletes memory + mChunkLen = 0; + } + + mChannel = nsnull; + + // Notify listener if we hit an error or finished + if (NS_FAILED(mStatus) || mCurrentSize == mTotalSize) { + CallOnStopRequest(); + return NS_OK; + } + + return StartTimer(mInterval); // Do next chunk +} + +// nsIStreamListener + +NS_IMETHODIMP +nsIncrementalDownload::OnDataAvailable(nsIRequest *request, + nsISupports *context, + nsIInputStream *input, + PRUint32 offset, + PRUint32 count) +{ + while (count) { + PRUint32 space = mChunkSize - mChunkLen; + PRUint32 n, len = PR_MIN(space, count); + + nsresult rv = input->Read(mChunk + mChunkLen, len, &n); + if (NS_FAILED(rv)) + return rv; + if (n != len) + return NS_ERROR_UNEXPECTED; + + count -= n; + mChunkLen += n; + + if (mChunkLen == mChunkSize) + FlushChunk(); + } + + return NS_OK; +} + +// nsIObserver + +NS_IMETHODIMP +nsIncrementalDownload::Observe(nsISupports *subject, const char *topic, + const PRUnichar *data) +{ + if (strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) { + Cancel(NS_ERROR_ABORT); + + // Since the app is shutting down, we need to go ahead and notify our + // observer here. Otherwise, we would notify them after XPCOM has been + // shutdown or not at all. + CallOnStopRequest(); + } + else if (strcmp(topic, NS_TIMER_CALLBACK_TOPIC) == 0) { + mTimer = nsnull; + nsresult rv = ProcessTimeout(); + if (NS_FAILED(rv)) + Cancel(rv); + } + return NS_OK; +} + +extern NS_METHOD +net_NewIncrementalDownload(nsISupports *outer, const nsIID &iid, void **result) +{ + if (outer) + return NS_ERROR_NO_AGGREGATION; + + nsIncrementalDownload *d = new nsIncrementalDownload(); + if (!d) + return NS_ERROR_OUT_OF_MEMORY; + + NS_ADDREF(d); + nsresult rv = d->QueryInterface(iid, result); + NS_RELEASE(d); + return rv; +} diff --git a/mozilla/netwerk/build/nsNetCID.h b/mozilla/netwerk/build/nsNetCID.h index 3a066824c80..ea5d302d6e7 100644 --- a/mozilla/netwerk/build/nsNetCID.h +++ b/mozilla/netwerk/build/nsNetCID.h @@ -335,6 +335,10 @@ {0xa9, 0x04, 0xac, 0x1d, 0x6d, 0xa7, 0x7a, 0x02} \ } +// component implementing nsIIncrementalDownload. +#define NS_INCREMENTALDOWNLOAD_CONTRACTID \ + "@mozilla.org/network/incremental-download;1" + // service implementing nsIStreamTransportService #define NS_STREAMTRANSPORTSERVICE_CLASSNAME \ "nsStreamTransportService" diff --git a/mozilla/netwerk/build/nsNetModule.cpp b/mozilla/netwerk/build/nsNetModule.cpp index 55b7d6f1cbb..45b9042a1b6 100644 --- a/mozilla/netwerk/build/nsNetModule.cpp +++ b/mozilla/netwerk/build/nsNetModule.cpp @@ -110,6 +110,21 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(nsSafeFileOutputStream) /////////////////////////////////////////////////////////////////////////////// +extern NS_METHOD +net_NewIncrementalDownload(nsISupports *, const nsIID &, void **); + +#define NS_INCREMENTALDOWNLOAD_CLASSNAME \ + "nsIncrementalDownload" +#define NS_INCREMENTALDOWNLOAD_CID \ +{ /* a62af1ba-79b3-4896-8aaf-b148bfce4280 */ \ + 0xa62af1ba, \ + 0x79b3, \ + 0x4896, \ + {0x8a, 0xaf, 0xb1, 0x48, 0xbf, 0xce, 0x42, 0x80} \ +} + +/////////////////////////////////////////////////////////////////////////////// + #include "nsStreamConverterService.h" #ifdef BUILD_APPLEFILE_DECODER @@ -706,6 +721,12 @@ static const nsModuleComponentInfo gNetModuleInfo[] = { nsURICheckerConstructor }, + { NS_INCREMENTALDOWNLOAD_CLASSNAME, + NS_INCREMENTALDOWNLOAD_CID, + NS_INCREMENTALDOWNLOAD_CONTRACTID, + net_NewIncrementalDownload + }, + // The register functions for the built-in // parsers just need to be called once. { NS_STDURLPARSER_CLASSNAME, diff --git a/mozilla/netwerk/test/Makefile.in b/mozilla/netwerk/test/Makefile.in index 65c3ee5c76f..e4d427efdc3 100644 --- a/mozilla/netwerk/test/Makefile.in +++ b/mozilla/netwerk/test/Makefile.in @@ -54,6 +54,7 @@ REQUIRES = xpcom \ $(NULL) CPPSRCS = \ + TestIncrementalDownload.cpp \ TestStreamLoader.cpp \ PropertiesTest.cpp \ urltest.cpp \ diff --git a/mozilla/netwerk/test/TestIncrementalDownload.cpp b/mozilla/netwerk/test/TestIncrementalDownload.cpp new file mode 100644 index 00000000000..5bb0bbe1ed5 --- /dev/null +++ b/mozilla/netwerk/test/TestIncrementalDownload.cpp @@ -0,0 +1,198 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 cin et: */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is mozilla.org code. + * + * The Initial Developer of the Original Code is Google Inc. + * Portions created by the Initial Developer are Copyright (C) 2005 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Darin Fisher + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include +#include "nsNetUtil.h" +#include "nsIIncrementalDownload.h" +#include "nsIRequestObserver.h" +#include "nsIProgressEventSink.h" +#include "nsIEventQueueService.h" +#include "nsIEventQueue.h" +#include "nsAutoPtr.h" +#include "prprf.h" +#include "prenv.h" + +//----------------------------------------------------------------------------- + +static nsresult SetupEventQ() +{ + nsresult rv; + + nsCOMPtr eqs = + do_GetService(NS_EVENTQUEUESERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) + return rv; + + return eqs->CreateMonitoredThreadEventQueue(); +} + +static nsresult PumpEvents() +{ + nsresult rv; + + nsCOMPtr eqs = + do_GetService(NS_EVENTQUEUESERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr eq; + rv = eqs->GetThreadEventQueue(NS_CURRENT_THREAD, getter_AddRefs(eq)); + if (NS_FAILED(rv)) + return rv; + + return eq->EventLoop(); +} + +static void QuitPumpingEvents() +{ + PR_Interrupt(PR_GetCurrentThread()); +} + +//----------------------------------------------------------------------------- + +class FetchObserver : public nsIRequestObserver + , public nsIProgressEventSink +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSIPROGRESSEVENTSINK +}; + +NS_IMPL_ISUPPORTS2(FetchObserver, nsIRequestObserver, + nsIProgressEventSink) + +NS_IMETHODIMP +FetchObserver::OnStartRequest(nsIRequest *request, nsISupports *context) +{ + printf("FetchObserver::OnStartRequest\n"); + return NS_OK; +} + +NS_IMETHODIMP +FetchObserver::OnProgress(nsIRequest *request, nsISupports *context, + PRUint64 progress, PRUint64 progressMax) +{ + printf("FetchObserver::OnProgress [%lu/%lu]\n", + PRUint32(progress), PRUint32(progressMax)); + return NS_OK; +} + +NS_IMETHODIMP +FetchObserver::OnStatus(nsIRequest *request, nsISupports *context, + nsresult status, const PRUnichar *statusText) +{ + return NS_OK; +} + +NS_IMETHODIMP +FetchObserver::OnStopRequest(nsIRequest *request, nsISupports *context, + nsresult status) +{ + printf("FetchObserver::OnStopRequest [status=%x]\n", status); + + QuitPumpingEvents(); + return NS_OK; +} + +//----------------------------------------------------------------------------- + +static nsresult +DoIncrementalFetch(const char *uriSpec, const char *resultPath, PRInt32 chunkSize, + PRInt32 interval) +{ + nsCOMPtr resultFile; + nsresult rv = NS_NewNativeLocalFile(nsDependentCString(resultPath), + PR_FALSE, getter_AddRefs(resultFile)); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr uri; + rv = NS_NewURI(getter_AddRefs(uri), uriSpec); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr observer = new FetchObserver(); + if (!observer) + return NS_ERROR_OUT_OF_MEMORY; + + nsCOMPtr download = + do_CreateInstance(NS_INCREMENTALDOWNLOAD_CONTRACTID, &rv); + if (NS_FAILED(rv)) + return rv; + + rv = download->Init(uri, resultFile, chunkSize, interval); + if (NS_FAILED(rv)) + return rv; + + rv = SetupEventQ(); + if (NS_FAILED(rv)) + return rv; + + rv = download->Start(observer, nsnull); + if (NS_FAILED(rv)) + return rv; + + return PumpEvents(); +} + +int +main(int argc, char **argv) +{ + if (PR_GetEnv("MOZ_BREAK_ON_MAIN")) + NS_BREAK(); + + if (argc < 5) { + fprintf(stderr, "USAGE: TestIncrementalDownload \n"); + return -1; + } + + nsresult rv = NS_InitXPCOM2(nsnull, nsnull, nsnull); + if (NS_FAILED(rv)) + return -1; + + PRInt32 chunkSize = atoi(argv[3]); + PRInt32 interval = atoi(argv[4]); + + rv = DoIncrementalFetch(argv[1], argv[2], chunkSize, interval); + if (NS_FAILED(rv)) + fprintf(stderr, "ERROR: DoIncrementalFetch failed [%x]\n", rv); + + NS_ShutdownXPCOM(nsnull); + return 0; +}