/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: NPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Netscape 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/NPL/ * * 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 * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1998 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Blake Ross (Original Author) * Ben Goodger (Original Author) * * 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 NPL, 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 NPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "nsDownloadManager.h" #include "nsIWebProgress.h" #include "nsIRDFLiteral.h" #include "rdf.h" #include "nsNetUtil.h" #include "nsIDOMWindow.h" #include "nsIDOMWindowInternal.h" #include "nsIDOMEvent.h" #include "nsIDOMEventTarget.h" #include "nsRDFCID.h" #include "nsAppDirectoryServiceDefs.h" #include "nsIWebBrowserPersist.h" #include "nsIObserver.h" #include "nsIProgressDialog.h" #include "nsIWebBrowserPersist.h" #include "nsIWindowWatcher.h" #include "nsIStringBundle.h" #include "nsCRT.h" #include "nsIWindowMediator.h" #include "nsIPromptService.h" #include "nsIObserverService.h" #include "nsIProfileChangeStatus.h" #include "nsISound.h" #include "nsIPrefService.h" #include "nsIFileURL.h" /* Outstanding issues/todo: * 1. Implement pause/resume. */ static NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID); static NS_DEFINE_CID(kStringBundleServiceCID, NS_STRINGBUNDLESERVICE_CID); #define DOWNLOAD_MANAGER_FE_URL "chrome://communicator/content/downloadmanager/downloadmanager.xul" #define DOWNLOAD_MANAGER_BUNDLE "chrome://communicator/locale/downloadmanager/downloadmanager.properties" #define INTERVAL 500 static nsIRDFResource* gNC_DownloadsRoot = nsnull; static nsIRDFResource* gNC_File = nsnull; static nsIRDFResource* gNC_URL = nsnull; static nsIRDFResource* gNC_Name = nsnull; static nsIRDFResource* gNC_ProgressMode = nsnull; static nsIRDFResource* gNC_ProgressPercent = nsnull; static nsIRDFResource* gNC_Transferred = nsnull; static nsIRDFResource* gNC_DownloadState = nsnull; static nsIRDFResource* gNC_StatusText = nsnull; static nsIRDFService* gRDFService = nsnull; static PRInt32 gRefCnt = 0; /** * This function extracts the local file path corresponding to the given URI. */ static nsresult GetFilePathUTF8(nsIURI *aURI, nsACString &aResult) { nsresult rv; nsCOMPtr fileURL = do_QueryInterface(aURI, &rv); if (NS_FAILED(rv)) return rv; nsCOMPtr file; rv = fileURL->GetFile(getter_AddRefs(file)); if (NS_FAILED(rv)) return rv; nsAutoString path; rv = file->GetPath(path); if (NS_SUCCEEDED(rv)) CopyUTF16toUTF8(path, aResult); return rv; } /////////////////////////////////////////////////////////////////////////////// // nsDownloadManager NS_IMPL_ISUPPORTS3(nsDownloadManager, nsIDownloadManager, nsIDOMEventListener, nsIObserver) nsDownloadManager::nsDownloadManager() : mBatches(0) { } nsDownloadManager::~nsDownloadManager() { if (--gRefCnt != 0 || !gRDFService) // Either somebody tried to use |CreateInstance| instead of // |GetService| or |Init| failed very early, so there's nothing to // do here. return; gRDFService->UnregisterDataSource(mDataSource); NS_IF_RELEASE(gNC_DownloadsRoot); NS_IF_RELEASE(gNC_File); NS_IF_RELEASE(gNC_URL); NS_IF_RELEASE(gNC_Name); NS_IF_RELEASE(gNC_ProgressMode); NS_IF_RELEASE(gNC_ProgressPercent); NS_IF_RELEASE(gNC_Transferred); NS_IF_RELEASE(gNC_DownloadState); NS_IF_RELEASE(gNC_StatusText); NS_RELEASE(gRDFService); } nsresult nsDownloadManager::Init() { if (gRefCnt++ != 0) { NS_NOTREACHED("download manager should be used as a service"); return NS_ERROR_UNEXPECTED; // This will make the |CreateInstance| fail. } if (!mCurrDownloads.Init()) return NS_ERROR_FAILURE; nsresult rv; mRDFContainerUtils = do_GetService("@mozilla.org/rdf/container-utils;1", &rv); if (NS_FAILED(rv)) return rv; nsCOMPtr obsService = do_GetService("@mozilla.org/observer-service;1", &rv); if (NS_FAILED(rv)) return rv; rv = CallGetService(kRDFServiceCID, &gRDFService); if (NS_FAILED(rv)) return rv; gRDFService->GetResource(NS_LITERAL_CSTRING("NC:DownloadsRoot"), &gNC_DownloadsRoot); gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "File"), &gNC_File); gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "URL"), &gNC_URL); gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "Name"), &gNC_Name); gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "ProgressMode"), &gNC_ProgressMode); gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "ProgressPercent"), &gNC_ProgressPercent); gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "Transferred"), &gNC_Transferred); gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "DownloadState"), &gNC_DownloadState); gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "StatusText"), &gNC_StatusText); nsCAutoString downloadsDB; rv = GetProfileDownloadsFileURL(downloadsDB); if (NS_FAILED(rv)) return rv; rv = gRDFService->GetDataSourceBlocking(downloadsDB.get(), getter_AddRefs(mDataSource)); if (NS_FAILED(rv)) return rv; mListener = do_CreateInstance("@mozilla.org/download-manager/listener;1", &rv); if (NS_FAILED(rv)) return rv; nsCOMPtr bundleService = do_GetService(kStringBundleServiceCID, &rv); if (NS_FAILED(rv)) return rv; rv = bundleService->CreateBundle(DOWNLOAD_MANAGER_BUNDLE, getter_AddRefs(mBundle)); if (NS_FAILED(rv)) return rv; // failure to add an observer is not critical obsService->AddObserver(this, "profile-before-change", PR_FALSE); obsService->AddObserver(this, "profile-approve-change", PR_FALSE); return NS_OK; } nsresult nsDownloadManager::DownloadStarted(const nsACString& aTargetPath) { if (mCurrDownloads.GetWeak(aTargetPath)) AssertProgressInfoFor(aTargetPath); return NS_OK; } nsresult nsDownloadManager::DownloadEnded(const nsACString& aTargetPath, const PRUnichar* aMessage) { nsDownload* dl = mCurrDownloads.GetWeak(aTargetPath); if (dl) { AssertProgressInfoFor(aTargetPath); mCurrDownloads.Remove(aTargetPath); } return NS_OK; } nsresult nsDownloadManager::GetProfileDownloadsFileURL(nsCString& aDownloadsFileURL) { nsCOMPtr downloadsFile; nsresult rv = NS_GetSpecialDirectory(NS_APP_DOWNLOADS_50_FILE, getter_AddRefs(downloadsFile)); if (NS_FAILED(rv)) return rv; return NS_GetURLSpecFromFile(downloadsFile, aDownloadsFileURL); } nsresult nsDownloadManager::GetDownloadsContainer(nsIRDFContainer** aResult) { if (mDownloadsContainer) { *aResult = mDownloadsContainer; NS_ADDREF(*aResult); return NS_OK; } PRBool isContainer; nsresult rv = mRDFContainerUtils->IsContainer(mDataSource, gNC_DownloadsRoot, &isContainer); if (NS_FAILED(rv)) return rv; if (!isContainer) { rv = mRDFContainerUtils->MakeSeq(mDataSource, gNC_DownloadsRoot, getter_AddRefs(mDownloadsContainer)); if (NS_FAILED(rv)) return rv; } else { mDownloadsContainer = do_CreateInstance(NS_RDF_CONTRACTID "/container;1", &rv); if (NS_FAILED(rv)) return rv; rv = mDownloadsContainer->Init(mDataSource, gNC_DownloadsRoot); if (NS_FAILED(rv)) return rv; } *aResult = mDownloadsContainer; NS_IF_ADDREF(*aResult); return rv; } nsresult nsDownloadManager::GetInternalListener(nsIDownloadProgressListener** aInternalListener) { *aInternalListener = mListener; NS_IF_ADDREF(*aInternalListener); return NS_OK; } nsresult nsDownloadManager::GetDataSource(nsIRDFDataSource** aDataSource) { *aDataSource = mDataSource; NS_IF_ADDREF(*aDataSource); return NS_OK; } nsresult nsDownloadManager::AssertProgressInfo() { nsCOMPtr supports; nsCOMPtr res; nsCOMPtr intLiteral; gRDFService->GetIntLiteral(DOWNLOADING, getter_AddRefs(intLiteral)); nsCOMPtr downloads; nsresult rv = mDataSource->GetSources(gNC_DownloadState, intLiteral, PR_TRUE, getter_AddRefs(downloads)); if (NS_FAILED(rv)) return rv; PRBool hasMoreElements; downloads->HasMoreElements(&hasMoreElements); while (hasMoreElements) { const char* uri; downloads->GetNext(getter_AddRefs(supports)); res = do_QueryInterface(supports); res->GetValueConst(&uri); AssertProgressInfoFor(nsDependentCString(uri)); downloads->HasMoreElements(&hasMoreElements); } return rv; } nsresult nsDownloadManager::AssertProgressInfoFor(const nsACString& aTargetPath) { nsDownload* internalDownload = mCurrDownloads.GetWeak(aTargetPath); if (!internalDownload) return NS_ERROR_FAILURE; nsresult rv; PRInt32 percentComplete; nsCOMPtr oldTarget; nsCOMPtr intLiteral; nsCOMPtr res; nsCOMPtr literal; gRDFService->GetResource(aTargetPath, getter_AddRefs(res)); DownloadState state; internalDownload->GetDownloadState(&state); // update progress mode nsAutoString progressMode; if (state == DOWNLOADING) progressMode.Assign(NS_LITERAL_STRING("normal")); else progressMode.Assign(NS_LITERAL_STRING("none")); gRDFService->GetLiteral(progressMode.get(), getter_AddRefs(literal)); rv = mDataSource->GetTarget(res, gNC_ProgressMode, PR_TRUE, getter_AddRefs(oldTarget)); if (oldTarget) rv = mDataSource->Change(res, gNC_ProgressMode, oldTarget, literal); else rv = mDataSource->Assert(res, gNC_ProgressMode, literal, PR_TRUE); if (NS_FAILED(rv)) return rv; // update download state (not started, downloading, queued, finished, etc...) gRDFService->GetIntLiteral(state, getter_AddRefs(intLiteral)); mDataSource->GetTarget(res, gNC_DownloadState, PR_TRUE, getter_AddRefs(oldTarget)); if (oldTarget) { rv = mDataSource->Change(res, gNC_DownloadState, oldTarget, intLiteral); if (NS_FAILED(rv)) return rv; } nsAutoString strKey; if (state == NOTSTARTED) strKey.Assign(NS_LITERAL_STRING("notStarted")); else if (state == DOWNLOADING) strKey.Assign(NS_LITERAL_STRING("downloading")); else if (state == FINISHED) strKey.Assign(NS_LITERAL_STRING("finished")); else if (state == FAILED) strKey.Assign(NS_LITERAL_STRING("failed")); else if (state == CANCELED) strKey.Assign(NS_LITERAL_STRING("canceled")); nsXPIDLString value; rv = mBundle->GetStringFromName(strKey.get(), getter_Copies(value)); if (NS_FAILED(rv)) return rv; gRDFService->GetLiteral(value, getter_AddRefs(literal)); rv = mDataSource->GetTarget(res, gNC_StatusText, PR_TRUE, getter_AddRefs(oldTarget)); if (oldTarget) { rv = mDataSource->Change(res, gNC_StatusText, oldTarget, literal); if (NS_FAILED(rv)) return rv; } else { rv = mDataSource->Assert(res, gNC_StatusText, literal, PR_TRUE); if (NS_FAILED(rv)) return rv; } // update percentage internalDownload->GetPercentComplete(&percentComplete); mDataSource->GetTarget(res, gNC_ProgressPercent, PR_TRUE, getter_AddRefs(oldTarget)); gRDFService->GetIntLiteral(percentComplete, getter_AddRefs(intLiteral)); if (oldTarget) rv = mDataSource->Change(res, gNC_ProgressPercent, oldTarget, intLiteral); else rv = mDataSource->Assert(res, gNC_ProgressPercent, intLiteral, PR_TRUE); if (NS_FAILED(rv)) return rv; // update transferred PRInt32 current = 0; PRInt32 max = 0; internalDownload->GetTransferInformation(¤t, &max); nsAutoString currBytes; currBytes.AppendInt(current); nsAutoString maxBytes; maxBytes.AppendInt(max); const PRUnichar *strings[] = { currBytes.get(), maxBytes.get() }; rv = mBundle->FormatStringFromName(NS_LITERAL_STRING("transferred").get(), strings, 2, getter_Copies(value)); if (NS_FAILED(rv)) return rv; gRDFService->GetLiteral(value, getter_AddRefs(literal)); mDataSource->GetTarget(res, gNC_Transferred, PR_TRUE, getter_AddRefs(oldTarget)); if (oldTarget) rv = mDataSource->Change(res, gNC_Transferred, oldTarget, literal); else rv = mDataSource->Assert(res, gNC_Transferred, literal, PR_TRUE); if (NS_FAILED(rv)) return rv; nsCOMPtr remote = do_QueryInterface(mDataSource); remote->Flush(); // XXX should also store and update time elapsed return rv; } /////////////////////////////////////////////////////////////////////////////// // nsIDownloadManager NS_IMETHODIMP nsDownloadManager::AddDownload(nsIURI* aSource, nsIURI* aTarget, const PRUnichar* aDisplayName, nsIMIMEInfo *aMIMEInfo, PRInt64 aStartTime, nsIWebBrowserPersist* aPersist, nsIDownload** aDownload) { NS_ENSURE_ARG_POINTER(aSource); NS_ENSURE_ARG_POINTER(aTarget); NS_ENSURE_ARG_POINTER(aDownload); nsCOMPtr downloads; nsresult rv = GetDownloadsContainer(getter_AddRefs(downloads)); if (NS_FAILED(rv)) return rv; nsDownload* internalDownload = new nsDownload(); if (!internalDownload) return NS_ERROR_OUT_OF_MEMORY; NS_ADDREF(*aDownload = internalDownload); // give our new nsIDownload some info so it's ready to go off into the world internalDownload->SetDownloadManager(this); internalDownload->SetTarget(aTarget); internalDownload->SetSource(aSource); // the path of the target is the unique identifier we use nsCOMPtr targetFile; rv = internalDownload->GetTargetFile(getter_AddRefs(targetFile)); if (NS_FAILED(rv)) return rv; nsAutoString path; rv = targetFile->GetPath(path); if (NS_FAILED(rv)) return rv; NS_ConvertUCS2toUTF8 utf8Path(path); nsCOMPtr downloadRes; gRDFService->GetResource(utf8Path, getter_AddRefs(downloadRes)); nsCOMPtr node; // Assert source url information nsCAutoString spec; aSource->GetSpec(spec); nsCOMPtr urlResource; gRDFService->GetResource(spec, getter_AddRefs(urlResource)); mDataSource->GetTarget(downloadRes, gNC_URL, PR_TRUE, getter_AddRefs(node)); if (node) rv = mDataSource->Change(downloadRes, gNC_URL, node, urlResource); else rv = mDataSource->Assert(downloadRes, gNC_URL, urlResource, PR_TRUE); if (NS_FAILED(rv)) return rv; // Set and assert the "pretty" (display) name of the download nsAutoString displayName; displayName.Assign(aDisplayName); if (displayName.IsEmpty()) { targetFile->GetLeafName(displayName); } internalDownload->SetDisplayName(displayName.get()); nsCOMPtr nameLiteral; gRDFService->GetLiteral(displayName.get(), getter_AddRefs(nameLiteral)); mDataSource->GetTarget(downloadRes, gNC_Name, PR_TRUE, getter_AddRefs(node)); if (node) rv = mDataSource->Change(downloadRes, gNC_Name, node, nameLiteral); else rv = mDataSource->Assert(downloadRes, gNC_Name, nameLiteral, PR_TRUE); if (NS_FAILED(rv)) return rv; internalDownload->SetMIMEInfo(aMIMEInfo); internalDownload->SetStartTime(aStartTime); // Assert file information nsCOMPtr fileResource; gRDFService->GetResource(utf8Path, getter_AddRefs(fileResource)); rv = mDataSource->Assert(downloadRes, gNC_File, fileResource, PR_TRUE); if (NS_FAILED(rv)) return rv; // Assert download state information (NOTSTARTED, since it's just now being added) nsCOMPtr intLiteral; gRDFService->GetIntLiteral(NOTSTARTED, getter_AddRefs(intLiteral)); mDataSource->GetTarget(downloadRes, gNC_DownloadState, PR_TRUE, getter_AddRefs(node)); if (node) rv = mDataSource->Change(downloadRes, gNC_DownloadState, node, intLiteral); else rv = mDataSource->Assert(downloadRes, gNC_DownloadState, intLiteral, PR_TRUE); if (NS_FAILED(rv)) return rv; PRInt32 itemIndex; downloads->IndexOf(downloadRes, &itemIndex); if (itemIndex == -1) { rv = downloads->AppendElement(downloadRes); if (NS_FAILED(rv)) return rv; } // Now flush all this to disk nsCOMPtr remote(do_QueryInterface(mDataSource)); rv = remote->Flush(); if (NS_FAILED(rv)) return rv; // if a persist object was specified, set the download item as the progress listener // this will create a cycle that will be broken in nsDownload::OnStateChange if (aPersist) { internalDownload->SetPersist(aPersist); aPersist->SetProgressListener(internalDownload); } mCurrDownloads.Put(utf8Path, internalDownload); return rv; } NS_IMETHODIMP nsDownloadManager::GetDownload(const nsACString & aTargetPath, nsIDownload** aDownloadItem) { NS_ENSURE_ARG_POINTER(aDownloadItem); // if it's currently downloading we can get it from the table // XXX otherwise we should look for it in the datasource and // create a new nsIDownload with the resource's properties NS_IF_ADDREF(*aDownloadItem = mCurrDownloads.GetWeak(aTargetPath)); return NS_OK; } NS_IMETHODIMP nsDownloadManager::CancelDownload(const nsACString & aTargetPath) { nsresult rv = NS_OK; nsRefPtr internalDownload = mCurrDownloads.GetWeak(aTargetPath); if (!internalDownload) return NS_ERROR_FAILURE; // Don't cancel if download is already finished if (internalDownload->mDownloadState == FINISHED) return NS_OK; internalDownload->SetDownloadState(CANCELED); // if a persist was provided, we can do the cancel ourselves. nsCOMPtr persist; internalDownload->GetPersist(getter_AddRefs(persist)); if (persist) { rv = persist->CancelSave(); if (NS_FAILED(rv)) return rv; } // if an observer was provided, notify that the download was cancelled. // if no persist was provided, this is necessary so that whatever transfer // component being used can cancel the download itself. nsCOMPtr observer; internalDownload->GetObserver(getter_AddRefs(observer)); if (observer) { rv = observer->Observe(NS_STATIC_CAST(nsIDownload*, internalDownload), "oncancel", nsnull); if (NS_FAILED(rv)) return rv; } DownloadEnded(aTargetPath, nsnull); // if there's a progress dialog open for the item, // we have to notify it that we're cancelling nsCOMPtr dialog; internalDownload->GetDialog(getter_AddRefs(dialog)); if (dialog) { observer = do_QueryInterface(dialog); rv = observer->Observe(NS_STATIC_CAST(nsIDownload*, internalDownload), "oncancel", nsnull); if (NS_FAILED(rv)) return rv; } return rv; } NS_IMETHODIMP nsDownloadManager::RemoveDownload(const nsACString & aTargetPath) { // RemoveDownload is for downloads not currently in progress. Having it // cancel in-progress downloads would make things complicated, so just return. nsDownload* inProgress = mCurrDownloads.GetWeak(aTargetPath); NS_ASSERTION(!inProgress, "Can't call RemoveDownload on a download in progress!"); if (inProgress) return NS_ERROR_FAILURE; nsCOMPtr downloads; nsresult rv = GetDownloadsContainer(getter_AddRefs(downloads)); if (NS_FAILED(rv)) return rv; nsCOMPtr res; gRDFService->GetResource(aTargetPath, getter_AddRefs(res)); // remove all the arcs for this resource, and then remove it from the Seq nsCOMPtr arcs; rv = mDataSource->ArcLabelsOut(res, getter_AddRefs(arcs)); if (NS_FAILED(rv)) return rv; PRBool moreArcs; rv = arcs->HasMoreElements(&moreArcs); if (NS_FAILED(rv)) return rv; while (moreArcs) { nsCOMPtr supports; rv = arcs->GetNext(getter_AddRefs(supports)); if (NS_FAILED(rv)) return rv; nsCOMPtr arc(do_QueryInterface(supports, &rv)); if (NS_FAILED(rv)) return rv; nsCOMPtr targets; rv = mDataSource->GetTargets(res, arc, PR_TRUE, getter_AddRefs(targets)); if (NS_FAILED(rv)) return rv; PRBool moreTargets; rv = targets->HasMoreElements(&moreTargets); if (NS_FAILED(rv)) return rv; while (moreTargets) { rv = targets->GetNext(getter_AddRefs(supports)); if (NS_FAILED(rv)) return rv; nsCOMPtr target(do_QueryInterface(supports, &rv)); if (NS_FAILED(rv)) return rv; // and now drop this assertion from the graph rv = mDataSource->Unassert(res, arc, target); if (NS_FAILED(rv)) return rv; rv = targets->HasMoreElements(&moreTargets); if (NS_FAILED(rv)) return rv; } rv = arcs->HasMoreElements(&moreArcs); if (NS_FAILED(rv)) return rv; } PRInt32 itemIndex; downloads->IndexOf(res, &itemIndex); if (itemIndex <= 0) return NS_ERROR_FAILURE; nsCOMPtr node; rv = downloads->RemoveElementAt(itemIndex, PR_TRUE, getter_AddRefs(node)); if (NS_FAILED(rv)) return rv; // if a mass removal is being done, we don't want to flush every time if (mBatches) return rv; nsCOMPtr remote = do_QueryInterface(mDataSource); return remote->Flush(); } NS_IMETHODIMP nsDownloadManager::StartBatchUpdate() { ++mBatches; return NS_OK; } NS_IMETHODIMP nsDownloadManager::EndBatchUpdate() { nsresult rv = NS_OK; if (--mBatches == 0) { nsCOMPtr remote = do_QueryInterface(mDataSource); rv = remote->Flush(); } return rv; } NS_IMETHODIMP nsDownloadManager::Open(nsIDOMWindow* aParent, nsIDownload* aDownload) { // first assert new progress info so the ui is correctly updated // if this fails, it fails -- continue. AssertProgressInfo(); //check for an existing manager window and focus it nsresult rv; nsCOMPtr wm = do_GetService(NS_WINDOWMEDIATOR_CONTRACTID, &rv); if (NS_FAILED(rv)) return rv; nsCOMPtr dlSupports(do_QueryInterface(aDownload)); // if the window's already open, do nothing (focusing it would be annoying) nsCOMPtr recentWindow; wm->GetMostRecentWindow(NS_LITERAL_STRING("Download:Manager").get(), getter_AddRefs(recentWindow)); if (recentWindow) { nsCOMPtr obsService = do_GetService("@mozilla.org/observer-service;1", &rv); if (NS_FAILED(rv)) return rv; return obsService->NotifyObservers(dlSupports, "download-starting", nsnull); } // if we ever have the capability to display the UI of third party dl managers, // we'll open their UI here instead. nsCOMPtr ww = do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv); if (NS_FAILED(rv)) return rv; // pass the datasource to the window nsCOMPtr params(do_CreateInstance(NS_SUPPORTSARRAY_CONTRACTID)); nsCOMPtr dsSupports(do_QueryInterface(mDataSource)); params->AppendElement(dsSupports); params->AppendElement(dlSupports); nsCOMPtr newWindow; rv = ww->OpenWindow(aParent, DOWNLOAD_MANAGER_FE_URL, "_blank", "chrome,all,dialog=no,resizable", params, getter_AddRefs(newWindow)); if (NS_FAILED(rv)) return rv; nsCOMPtr target = do_QueryInterface(newWindow); if (!target) return NS_ERROR_FAILURE; rv = target->AddEventListener(NS_LITERAL_STRING("load"), this, PR_FALSE); if (NS_FAILED(rv)) return rv; return target->AddEventListener(NS_LITERAL_STRING("unload"), this, PR_FALSE); } NS_IMETHODIMP nsDownloadManager::OpenProgressDialogFor(nsIDownload* aDownload, nsIDOMWindow* aParent, PRBool aCancelDownloadOnClose) { NS_ENSURE_ARG_POINTER(aDownload); nsresult rv; nsDownload* internalDownload = NS_STATIC_CAST(nsDownload*, aDownload); nsCOMPtr oldDialog; internalDownload->GetDialog(getter_AddRefs(oldDialog)); if (oldDialog) { nsCOMPtr window; oldDialog->GetDialog(getter_AddRefs(window)); if (window) { nsCOMPtr internalWin = do_QueryInterface(window); internalWin->Focus(); return NS_OK; } } nsCOMPtr dialog(do_CreateInstance("@mozilla.org/progressdialog;1", &rv)); if (NS_FAILED(rv)) return rv; dialog->SetCancelDownloadOnClose(aCancelDownloadOnClose); // now give the dialog the necessary context // start time... PRInt64 startTime = 0; aDownload->GetStartTime(&startTime); // source... nsCOMPtr source; aDownload->GetSource(getter_AddRefs(source)); // target... nsCOMPtr target; aDownload->GetTarget(getter_AddRefs(target)); // helper app... nsCOMPtr mimeInfo; aDownload->GetMIMEInfo(getter_AddRefs(mimeInfo)); dialog->Init(source, target, nsnull, mimeInfo, startTime, nsnull); dialog->SetObserver(this); // now set the listener so we forward notifications to the dialog nsCOMPtr listener = do_QueryInterface(dialog); internalDownload->SetDialogListener(listener); internalDownload->SetDialog(dialog); return dialog->Open(aParent); } NS_IMETHODIMP nsDownloadManager::OnClose() { mDocument = nsnull; mListener->SetDocument(nsnull); return NS_OK; } /////////////////////////////////////////////////////////////////////////////// // nsIDOMEventListener NS_IMETHODIMP nsDownloadManager::HandleEvent(nsIDOMEvent* aEvent) { // the event is either load or unload nsAutoString eventType; aEvent->GetType(eventType); if (eventType.Equals(NS_LITERAL_STRING("unload"))) return OnClose(); nsCOMPtr target; nsresult rv = aEvent->GetTarget(getter_AddRefs(target)); if (NS_FAILED(rv)) return rv; mDocument = do_QueryInterface(target); mListener->SetDocument(mDocument); return NS_OK; } /////////////////////////////////////////////////////////////////////////////// // nsIObserver NS_IMETHODIMP nsDownloadManager::Observe(nsISupports* aSubject, const char* aTopic, const PRUnichar* aData) { nsresult rv; if (nsCRT::strcmp(aTopic, "oncancel") == 0) { nsCOMPtr dialog = do_QueryInterface(aSubject); nsCOMPtr target; dialog->GetTarget(getter_AddRefs(target)); nsCAutoString path; rv = GetFilePathUTF8(target, path); if (NS_FAILED(rv)) return rv; nsDownload* download = mCurrDownloads.GetWeak(path); if (download) { // unset dialog since it's closing download->SetDialog(nsnull); return CancelDownload(path); } } else if (nsCRT::strcmp(aTopic, "profile-approve-change") == 0) { // Only run this on profile switch if (!NS_LITERAL_STRING("switch").Equals(aData)) return NS_OK; // If count == 0, nothing to do if (mCurrDownloads.Count() == 0) return NS_OK; nsCOMPtr changeStatus(do_QueryInterface(aSubject)); if (!changeStatus) return NS_ERROR_UNEXPECTED; nsXPIDLString title, text, proceed, cancel; nsresult rv = mBundle->GetStringFromName(NS_LITERAL_STRING("profileSwitchTitle").get(), getter_Copies(title)); NS_ENSURE_SUCCESS(rv, rv); rv = mBundle->GetStringFromName(NS_LITERAL_STRING("profileSwitchText").get(), getter_Copies(text)); NS_ENSURE_SUCCESS(rv, rv); rv = mBundle->GetStringFromName(NS_LITERAL_STRING("profileSwitchContinue").get(), getter_Copies(proceed)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr promptService(do_GetService("@mozilla.org/embedcomp/prompt-service;1", &rv)); if (NS_FAILED(rv)) return rv; PRInt32 button; rv = promptService->ConfirmEx(nsnull, title.get(), text.get(), nsIPromptService::BUTTON_TITLE_CANCEL * nsIPromptService::BUTTON_POS_0 | nsIPromptService::BUTTON_TITLE_IS_STRING * nsIPromptService::BUTTON_POS_1, nsnull, proceed.get(), nsnull, nsnull, nsnull, &button); if (NS_FAILED(rv)) return rv; if (button == 0) changeStatus->VetoChange(); } else if (nsCRT::strcmp(aTopic, "profile-before-change") == 0) { nsCOMPtr supports; nsCOMPtr res; nsCOMPtr intLiteral; gRDFService->GetIntLiteral(DOWNLOADING, getter_AddRefs(intLiteral)); nsCOMPtr downloads; rv = mDataSource->GetSources(gNC_DownloadState, intLiteral, PR_TRUE, getter_AddRefs(downloads)); if (NS_FAILED(rv)) return rv; PRBool hasMoreElements; downloads->HasMoreElements(&hasMoreElements); while (hasMoreElements) { const char* uri; downloads->GetNext(getter_AddRefs(supports)); res = do_QueryInterface(supports); res->GetValueConst(&uri); CancelDownload(nsDependentCString(uri)); downloads->HasMoreElements(&hasMoreElements); } } return NS_OK; } /////////////////////////////////////////////////////////////////////////////// // nsDownload NS_IMPL_ISUPPORTS3(nsDownload, nsIDownload, nsITransfer, nsIWebProgressListener) nsDownload::nsDownload():mDownloadState(NOTSTARTED), mPercentComplete(0), mCurrBytes(0), mMaxBytes(0), mStartTime(0), mLastUpdate(-500) { } nsDownload::~nsDownload() { nsCAutoString path; nsresult rv = GetFilePathUTF8(mTarget, path); if (NS_FAILED(rv)) return; mDownloadManager->AssertProgressInfoFor(path); } nsresult nsDownload::SetDownloadManager(nsDownloadManager* aDownloadManager) { mDownloadManager = aDownloadManager; return NS_OK; } nsresult nsDownload::SetDialogListener(nsIWebProgressListener* aDialogListener) { mDialogListener = aDialogListener; return NS_OK; } nsresult nsDownload::GetDialogListener(nsIWebProgressListener** aDialogListener) { *aDialogListener = mDialogListener; NS_IF_ADDREF(*aDialogListener); return NS_OK; } nsresult nsDownload::SetDialog(nsIProgressDialog* aDialog) { mDialog = aDialog; return NS_OK; } nsresult nsDownload::GetDialog(nsIProgressDialog** aDialog) { *aDialog = mDialog; NS_IF_ADDREF(*aDialog); return NS_OK; } nsresult nsDownload::GetDownloadState(DownloadState* aState) { *aState = mDownloadState; return NS_OK; } nsresult nsDownload::SetDownloadState(DownloadState aState) { mDownloadState = aState; return NS_OK; } nsresult nsDownload::SetPersist(nsIWebBrowserPersist* aPersist) { mPersist = aPersist; return NS_OK; } nsresult nsDownload::SetSource(nsIURI* aSource) { mSource = aSource; return NS_OK; } nsresult nsDownload::SetTarget(nsIURI* aTarget) { mTarget = aTarget; return NS_OK; } nsresult nsDownload::GetTransferInformation(PRInt32* aCurr, PRInt32* aMax) { *aCurr = mCurrBytes; *aMax = mMaxBytes; return NS_OK; } nsresult nsDownload::SetStartTime(PRInt64 aStartTime) { mStartTime = aStartTime; return NS_OK; } nsresult nsDownload::SetMIMEInfo(nsIMIMEInfo *aMIMEInfo) { mMIMEInfo = aMIMEInfo; return NS_OK; } /////////////////////////////////////////////////////////////////////////////// // nsIWebProgressListener NS_IMETHODIMP nsDownload::OnProgressChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, PRInt32 aCurSelfProgress, PRInt32 aMaxSelfProgress, PRInt32 aCurTotalProgress, PRInt32 aMaxTotalProgress) { if (!mRequest) mRequest = aRequest; // used for pause/resume // filter notifications since they come in so frequently PRTime delta; PRTime now = PR_Now(); LL_SUB(delta, now, mLastUpdate); if (LL_CMP(delta, <, INTERVAL) && aMaxTotalProgress != -1 && aCurTotalProgress < aMaxTotalProgress) return NS_OK; mLastUpdate = now; if (mDownloadState == NOTSTARTED) { nsCAutoString path; nsresult rv = GetFilePathUTF8(mTarget, path); if (NS_FAILED(rv)) return rv; mDownloadState = DOWNLOADING; mDownloadManager->DownloadStarted(path); } if (aMaxTotalProgress > 0) mPercentComplete = aCurTotalProgress * 100 / aMaxTotalProgress; else mPercentComplete = -1; mCurrBytes = (PRInt32)((PRFloat64)aCurTotalProgress / 1024.0 + .5); mMaxBytes = (PRInt32)((PRFloat64)aMaxTotalProgress / 1024 + .5); if (mListener) { mListener->OnProgressChange(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress); } if (mDownloadManager->MustUpdateUI()) { nsCOMPtr internalListener; mDownloadManager->GetInternalListener(getter_AddRefs(internalListener)); if (internalListener) { internalListener->OnProgressChange(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress, this); } } if (mDialogListener) { mDialogListener->OnProgressChange(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress); } return NS_OK; } NS_IMETHODIMP nsDownload::OnLocationChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, nsIURI *aLocation) { if (mListener) mListener->OnLocationChange(aWebProgress, aRequest, aLocation); if (mDownloadManager->MustUpdateUI()) { nsCOMPtr internalListener; mDownloadManager->GetInternalListener(getter_AddRefs(internalListener)); if (internalListener) internalListener->OnLocationChange(aWebProgress, aRequest, aLocation, this); } if (mDialogListener) mDialogListener->OnLocationChange(aWebProgress, aRequest, aLocation); return NS_OK; } NS_IMETHODIMP nsDownload::OnStatusChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, nsresult aStatus, const PRUnichar *aMessage) { if (NS_FAILED(aStatus)) { mDownloadState = FAILED; nsCAutoString path; nsresult rv = GetFilePathUTF8(mTarget, path); if (NS_SUCCEEDED(rv)) mDownloadManager->DownloadEnded(path, aMessage); } if (mListener) mListener->OnStatusChange(aWebProgress, aRequest, aStatus, aMessage); if (mDownloadManager->MustUpdateUI()) { nsCOMPtr internalListener; mDownloadManager->GetInternalListener(getter_AddRefs(internalListener)); if (internalListener) internalListener->OnStatusChange(aWebProgress, aRequest, aStatus, aMessage, this); } if (mDialogListener) mDialogListener->OnStatusChange(aWebProgress, aRequest, aStatus, aMessage); else { // Need to display error alert ourselves, if an error occurred. if (NS_FAILED(aStatus)) { // Get title for alert. nsXPIDLString title; nsresult rv; nsCOMPtr bundleService = do_GetService(kStringBundleServiceCID, &rv); nsCOMPtr bundle; if (bundleService) rv = bundleService->CreateBundle(DOWNLOAD_MANAGER_BUNDLE, getter_AddRefs(bundle)); if (bundle) bundle->GetStringFromName(NS_LITERAL_STRING("alertTitle").get(), getter_Copies(title)); // Get Download Manager window, to be parent of alert. nsCOMPtr wm = do_GetService(NS_WINDOWMEDIATOR_CONTRACTID, &rv); nsCOMPtr dmWindow; if (wm) wm->GetMostRecentWindow(NS_LITERAL_STRING("Download:Manager").get(), getter_AddRefs(dmWindow)); // Show alert. nsCOMPtr prompter(do_GetService("@mozilla.org/embedcomp/prompt-service;1")); if (prompter) prompter->Alert(dmWindow, title, aMessage); } } return NS_OK; } NS_IMETHODIMP nsDownload::OnStateChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest, PRUint32 aStateFlags, nsresult aStatus) { if (aStateFlags & STATE_START) mStartTime = PR_Now(); // When we break the ref cycle with mPersist, we don't want to lose // access to out member vars! nsRefPtr kungFuDeathGrip(this); if (mListener) mListener->OnStateChange(aWebProgress, aRequest, aStateFlags, aStatus); // We need to update mDownloadState before updating the dialog, because // that will close and call CancelDownload if it was the last open window. nsresult rv = NS_OK; if (aStateFlags & STATE_STOP) { if (mDownloadState == DOWNLOADING || mDownloadState == NOTSTARTED) { mDownloadState = FINISHED; // Files less than 1Kb shouldn't show up as 0Kb. if (mMaxBytes==0) mMaxBytes = 1; mCurrBytes = mMaxBytes; mPercentComplete = 100; //Play a sound when the download finishes nsXPIDLCString soundStr; nsCOMPtr prefs = do_GetService("@mozilla.org/preferences-service;1"); if (prefs) { nsCOMPtr prefBranch; prefs->GetBranch(nsnull, getter_AddRefs(prefBranch)); if (prefBranch) prefBranch->GetCharPref("browser.download.finished_sound_url", getter_Copies(soundStr)); } //only continue if it isn't blank if (!soundStr.IsEmpty()) { nsCOMPtr snd = do_GetService("@mozilla.org/sound;1"); if (snd) { //hopefully we got it nsCOMPtr soundURI; NS_NewURI(getter_AddRefs(soundURI), soundStr); nsCOMPtr soundURL(do_QueryInterface(soundURI)); if (soundURL) snd->Play(soundURL); else snd->Beep(); } } nsCAutoString path; rv = GetFilePathUTF8(mTarget, path); // can't do an early return; have to break reference cycle below if (NS_SUCCEEDED(rv)) { mDownloadManager->DownloadEnded(path, nsnull); } } // break the cycle we created in AddDownload if (mPersist) mPersist->SetProgressListener(nsnull); } if (mDownloadManager->MustUpdateUI()) { nsCOMPtr internalListener; mDownloadManager->GetInternalListener(getter_AddRefs(internalListener)); if (internalListener) internalListener->OnStateChange(aWebProgress, aRequest, aStateFlags, aStatus, this); } if (mDialogListener) mDialogListener->OnStateChange(aWebProgress, aRequest, aStateFlags, aStatus); return rv; } NS_IMETHODIMP nsDownload::OnSecurityChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, PRUint32 aState) { if (mListener) mListener->OnSecurityChange(aWebProgress, aRequest, aState); if (mDownloadManager->MustUpdateUI()) { nsCOMPtr internalListener; mDownloadManager->GetInternalListener(getter_AddRefs(internalListener)); if (internalListener) internalListener->OnSecurityChange(aWebProgress, aRequest, aState, this); } if (mDialogListener) mDialogListener->OnSecurityChange(aWebProgress, aRequest, aState); return NS_OK; } /////////////////////////////////////////////////////////////////////////////// // nsIDownload NS_IMETHODIMP nsDownload::Init(nsIURI* aSource, nsIURI* aTarget, const PRUnichar* aDisplayName, nsIMIMEInfo *aMIMEInfo, PRInt64 aStartTime, nsIWebBrowserPersist* aPersist) { NS_WARNING("Huh...how did we get here?!"); return NS_OK; } NS_IMETHODIMP nsDownload::SetDisplayName(const PRUnichar* aDisplayName) { mDisplayName = aDisplayName; nsCOMPtr ds; mDownloadManager->GetDataSource(getter_AddRefs(ds)); nsCOMPtr nameLiteral; nsCOMPtr res; nsCAutoString path; nsresult rv = GetFilePathUTF8(mTarget, path); if (NS_FAILED(rv)) return rv; gRDFService->GetResource(path, getter_AddRefs(res)); gRDFService->GetLiteral(aDisplayName, getter_AddRefs(nameLiteral)); ds->Assert(res, gNC_Name, nameLiteral, PR_TRUE); return NS_OK; } NS_IMETHODIMP nsDownload::GetDisplayName(PRUnichar** aDisplayName) { *aDisplayName = ToNewUnicode(mDisplayName); return NS_OK; } NS_IMETHODIMP nsDownload::GetTarget(nsIURI** aTarget) { *aTarget = mTarget; NS_IF_ADDREF(*aTarget); return NS_OK; } NS_IMETHODIMP nsDownload::GetSource(nsIURI** aSource) { *aSource = mSource; NS_IF_ADDREF(*aSource); return NS_OK; } NS_IMETHODIMP nsDownload::GetPersist(nsIWebBrowserPersist** aPersist) { *aPersist = mPersist; NS_IF_ADDREF(*aPersist); return NS_OK; } NS_IMETHODIMP nsDownload::GetStartTime(PRInt64* aStartTime) { *aStartTime = mStartTime; return NS_OK; } NS_IMETHODIMP nsDownload::GetPercentComplete(PRInt32* aPercentComplete) { *aPercentComplete = mPercentComplete; return NS_OK; } NS_IMETHODIMP nsDownload::SetListener(nsIWebProgressListener* aListener) { mListener = aListener; return NS_OK; } NS_IMETHODIMP nsDownload::GetListener(nsIWebProgressListener** aListener) { *aListener = mListener; NS_IF_ADDREF(*aListener); return NS_OK; } NS_IMETHODIMP nsDownload::SetObserver(nsIObserver* aObserver) { mObserver = aObserver; return NS_OK; } NS_IMETHODIMP nsDownload::GetObserver(nsIObserver** aObserver) { *aObserver = mObserver; NS_IF_ADDREF(*aObserver); return NS_OK; } NS_IMETHODIMP nsDownload::GetMIMEInfo(nsIMIMEInfo** aMIMEInfo) { *aMIMEInfo = mMIMEInfo; NS_IF_ADDREF(*aMIMEInfo); return NS_OK; } NS_IMETHODIMP nsDownload::GetTargetFile(nsILocalFile** aTargetFile) { nsresult rv; nsCOMPtr fileURL = do_QueryInterface(mTarget, &rv); if (NS_FAILED(rv)) return rv; nsCOMPtr file; rv = fileURL->GetFile(getter_AddRefs(file)); if (NS_SUCCEEDED(rv)) rv = CallQueryInterface(file, aTargetFile); return rv; }