From 58689daf717ef1ed08ba41a4a25c066d424acc8c Mon Sep 17 00:00:00 2001 From: "law%netscape.com" Date: Wed, 20 Feb 2002 08:00:29 +0000 Subject: [PATCH] Bug 27609; change exthandler to use new progress dialog and properly report i/o errors; r=bzbarsky, sr=mscott --- uriloader/exthandler/Makefile.in | 2 + uriloader/exthandler/makefile.win | 2 + .../exthandler/nsExternalHelperAppService.cpp | 236 ++++++++++++++++-- .../exthandler/nsExternalHelperAppService.h | 5 +- 4 files changed, 222 insertions(+), 23 deletions(-) diff --git a/uriloader/exthandler/Makefile.in b/uriloader/exthandler/Makefile.in index 070263989f6..5c897b51114 100644 --- a/uriloader/exthandler/Makefile.in +++ b/uriloader/exthandler/Makefile.in @@ -49,8 +49,10 @@ REQUIRES = xpcom \ rdf \ webshell \ helperAppDlg \ + progressDlg \ plugin \ pref \ + intl \ $(NULL) OSHELPER = nsOSHelperAppService.cpp diff --git a/uriloader/exthandler/makefile.win b/uriloader/exthandler/makefile.win index c74f7968a31..35f277d35ef 100644 --- a/uriloader/exthandler/makefile.win +++ b/uriloader/exthandler/makefile.win @@ -29,8 +29,10 @@ REQUIRES = xpcom \ rdf \ webshell \ helperAppDlg \ + progressDlg \ plugin \ unicharutil \ + intl \ $(NULL) include <$(DEPTH)\config\config.mak> diff --git a/uriloader/exthandler/nsExternalHelperAppService.cpp b/uriloader/exthandler/nsExternalHelperAppService.cpp index a8b0c726335..8c049ef1e54 100644 --- a/uriloader/exthandler/nsExternalHelperAppService.cpp +++ b/uriloader/exthandler/nsExternalHelperAppService.cpp @@ -53,6 +53,7 @@ #include "nsIRefreshURI.h" #include "nsIDocumentLoader.h" #include "nsIHelperAppLauncherDialog.h" +#include "nsIProgressDialog.h" #include "nsITransport.h" #include "nsIFileTransportService.h" #include "nsCExternalHandlerService.h" // contains contractids for the helper app service @@ -75,6 +76,8 @@ #include "nsIPluginHost.h" #include "nsEscape.h" +#include "nsIStringBundle.h" + const char *FORCE_ALWAYS_ASK_PREF = "browser.helperApps.alwaysAsk.force"; static NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID); @@ -673,6 +676,7 @@ NS_INTERFACE_MAP_BEGIN(nsExternalAppHandler) NS_INTERFACE_MAP_ENTRY(nsIHelperAppLauncher) NS_INTERFACE_MAP_ENTRY(nsIURIContentListener) NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor) + NS_INTERFACE_MAP_ENTRY(nsIObserver) NS_INTERFACE_MAP_END_THREADSAFE nsExternalAppHandler::nsExternalAppHandler() @@ -683,6 +687,8 @@ nsExternalAppHandler::nsExternalAppHandler() mStopRequestIssued = PR_FALSE; mDataBuffer = (char *) nsMemory::Alloc((sizeof(char) * DATA_BUFFER_SIZE)); mProgressWindowCreated = PR_FALSE; + mContentLength = -1; + mProgress = 0; } nsExternalAppHandler::~nsExternalAppHandler() @@ -697,6 +703,16 @@ NS_IMETHODIMP nsExternalAppHandler::GetInterface(const nsIID & aIID, void * *aIn return QueryInterface(aIID, aInstancePtr); } +NS_IMETHODIMP nsExternalAppHandler::Observe(nsISupports *aSubject, const char *aTopic, const PRUnichar *aData ) +{ + if (PL_strcmp(aTopic, "oncancel") == 0) + { + // User pressed cancel button on dialog. + return this->Cancel(); + } + return NS_OK; +} + NS_IMETHODIMP nsExternalAppHandler::SetWebProgressListener(nsIWebProgressListener * aWebProgressListener) { @@ -725,7 +741,6 @@ NS_IMETHODIMP nsExternalAppHandler::SetWebProgressListener(nsIWebProgressListene if (webProgress) { mWebProgressListener = aWebProgressListener; - webProgress->AddProgressListener(mWebProgressListener); } } return NS_OK; @@ -1011,6 +1026,11 @@ NS_IMETHODIMP nsExternalAppHandler::OnStartRequest(nsIRequest *request, nsISuppo nsresult rv = SetUpTempFile(aChannel); + // Get content length. + if ( aChannel ) + { + aChannel->GetContentLength( &mContentLength ); + } // retarget all load notifcations to our docloader instead of the original window's docloader... RetargetLoadNotifications(request); @@ -1023,6 +1043,8 @@ NS_IMETHODIMP nsExternalAppHandler::OnStartRequest(nsIRequest *request, nsISuppo httpChannel->SetApplyConversion( PR_FALSE ); } + mTimeDownloadStarted = PR_Now(); + // now that the temp file is set up, find out if we need to invoke a dialog asking the user what // they want us to do with this content... @@ -1059,14 +1081,71 @@ NS_IMETHODIMP nsExternalAppHandler::OnStartRequest(nsIRequest *request, nsISuppo } } - mTimeDownloadStarted = PR_Now(); - return NS_OK; } +// Convert error info into proper message text and send OnStatusChange notification +// to the web progress listener. +typedef enum { kReadError, kWriteError, kLaunchError } ErrorType; +static void SendStatusChange( + ErrorType type, nsresult rv, nsIRequest *aRequest, nsIWebProgressListener *aListener, const PRUnichar *path) +{ + nsAutoString msgId; + switch(rv) + { + case NS_ERROR_FILE_DISK_FULL: + case NS_ERROR_FILE_NO_DEVICE_SPACE: + // Out of space on target volume. + msgId = NS_LITERAL_STRING("diskFull"); + break; + + case NS_ERROR_FILE_READ_ONLY: + // Attempt to write to read/only file. + msgId = NS_LITERAL_STRING("readOnly"); + break; + + case NS_ERROR_FILE_ACCESS_DENIED: + // Attempt to write without sufficient permissions. + msgId = NS_LITERAL_STRING("accessError"); + break; + + default: + // Generic read/write/launch error message. + switch(type) + { + case kReadError: + msgId = NS_LITERAL_STRING("readError"); + break; + case kWriteError: + msgId = NS_LITERAL_STRING("writeError"); + break; + case kLaunchError: + msgId = NS_LITERAL_STRING("launchError"); + break; + } + break; + } + // Get properties file bundle and extract status string. + nsCOMPtr s = do_GetService(NS_STRINGBUNDLE_CONTRACTID); + if (s) + { + nsCOMPtr bundle; + if (NS_SUCCEEDED(s->CreateBundle("chrome://global/locale/nsWebBrowserPersist.properties", getter_AddRefs(bundle)))) + { + nsXPIDLString msgText; + const PRUnichar *strings[] = { path }; + if(NS_SUCCEEDED(bundle->FormatStringFromName(msgId.get(), strings, 1, getter_Copies(msgText)))) + { + aListener->OnStatusChange(nsnull, (type == kReadError) ? aRequest : nsnull, rv, msgText); + } + } + } +} + NS_IMETHODIMP nsExternalAppHandler::OnDataAvailable(nsIRequest *request, nsISupports * aCtxt, nsIInputStream * inStr, PRUint32 sourceOffset, PRUint32 count) { + nsresult rv = NS_OK; // first, check to see if we've been canceled.... if (mCanceled) // then go cancel our underlying channel too return request->Cancel(NS_BINDING_ABORTED); @@ -1076,17 +1155,73 @@ NS_IMETHODIMP nsExternalAppHandler::OnDataAvailable(nsIRequest *request, nsISupp { PRUint32 numBytesRead = 0; PRUint32 numBytesWritten = 0; - while (count > 0) // while we still have bytes to copy... + mProgress += count; + PRBool readError; + while (NS_SUCCEEDED(rv) && count > 0) // while we still have bytes to copy... { - inStr->Read(mDataBuffer, PR_MIN(count, DATA_BUFFER_SIZE - 1), &numBytesRead); - if (count >= numBytesRead) - count -= numBytesRead; // subtract off the number of bytes we just read - else - count = 0; - mOutStream->Write(mDataBuffer, numBytesRead, &numBytesWritten); + readError = PR_TRUE; + rv = inStr->Read(mDataBuffer, PR_MIN(count, DATA_BUFFER_SIZE - 1), &numBytesRead); + if (NS_SUCCEEDED(rv)) + { + if (count >= numBytesRead) + count -= numBytesRead; // subtract off the number of bytes we just read + else + count = 0; + readError = PR_FALSE; + // Write out the data until something goes wrong, or, it is + // all written. We loop because for some errors (e.g., disk + // full), we get NS_OK with some bytes written, then an error. + // So, we want to write again in that case to get the actual + // error code. + const char *bufPtr = mDataBuffer; // Where to write from. + while (NS_SUCCEEDED(rv) && numBytesRead) + { + numBytesWritten = 0; + rv = mOutStream->Write(bufPtr, numBytesRead, &numBytesWritten); + if (NS_SUCCEEDED(rv)) + { + numBytesRead -= numBytesWritten; + bufPtr += numBytesWritten; + // Force an error if (for some reason) we get NS_OK but + // no bytes written. + if (!numBytesWritten) + { + rv = NS_ERROR_FAILURE; + } + } + } + } + } + if (NS_SUCCEEDED(rv)) + { + // Set content length if we haven't already got it. + if (mContentLength == -1) + { + nsCOMPtr aChannel(do_QueryInterface(request)); + if (aChannel) + { + aChannel->GetContentLength(&mContentLength); + } + } + // Send progress notification. + if (mWebProgressListener) + { + mWebProgressListener->OnProgressChange(nsnull, request, mProgress, mContentLength, mProgress, mContentLength); + } + } + else + { + // An error occurred, notify listener. + if (mWebProgressListener) + { + nsXPIDLString tempFilePath; + if (mTempFile) + mTempFile->GetUnicodePath(getter_Copies(tempFilePath)); + SendStatusChange(readError ? kReadError : kWriteError, rv, request, mWebProgressListener, tempFilePath.get()); + } } } - return NS_OK; + return rv; } NS_IMETHODIMP nsExternalAppHandler::OnStopRequest(nsIRequest *request, nsISupports *aCtxt, @@ -1110,6 +1245,13 @@ NS_IMETHODIMP nsExternalAppHandler::OnStopRequest(nsIRequest *request, nsISuppor mOutStream = nsnull; } + // Notify dialog that download is complete. + if(mWebProgressListener) + { + // XXX Do we need to check for errors here (server goes down, network cable cut, etc.)? + mWebProgressListener->OnStateChange(nsnull, request, nsIWebProgressListener::STATE_STOP, NS_OK); + } + return ExecuteDesiredAction(); } @@ -1181,13 +1323,49 @@ nsresult nsExternalAppHandler::ShowProgressDialog() // done processing the load. in this case, throw up a progress dialog so the user can see what's going on... nsresult rv = NS_OK; - if ( !mDialog ) { - // Get helper app launcher dialog. - mDialog = do_CreateInstance( NS_IHELPERAPPLAUNCHERDLG_CONTRACTID, &rv ); - NS_ENSURE_SUCCESS(rv, rv); - } + nsCOMPtr progressDlg = do_CreateInstance( "@mozilla.org/progressdialog;1", &rv ); + if (progressDlg) + { + // Wire up this progress dialog. + progressDlg->SetSource( mSourceUrl ); + progressDlg->SetStartTime( mTimeDownloadStarted ); + progressDlg->SetObserver(this); + nsCOMPtr local = do_QueryInterface(mFinalFileDestination); + progressDlg->SetTarget(local); - rv = mDialog->ShowProgressDialog(this, mWindowContext); + nsMIMEInfoHandleAction action = nsIMIMEInfo::saveToDisk; + mMimeInfo->GetPreferredAction(&action); + if (action != nsIMIMEInfo::saveToDisk) + { + // Opening with an application; use either description or application file name. + nsXPIDLString openWith; + mMimeInfo->GetApplicationDescription(getter_Copies(openWith)); + if (openWith.IsEmpty()) + { + nsCOMPtr appl; + mMimeInfo->GetPreferredApplicationHandler(getter_AddRefs(appl)); + if (appl) + { + nsCOMPtr file = do_QueryInterface(appl); + if (file) + { + file->GetUnicodeLeafName(getter_Copies(openWith)); + } + } + } + // Tell progress dialog what we're opening with. + progressDlg->SetOpeningWith(openWith); + } + + // Open the dialog. + rv = progressDlg->Open(nsnull, nsnull); + + if(NS_SUCCEEDED(rv)) + { + // Send notifications to the dialog. + this->SetWebProgressListener(progressDlg); + } + } return rv; } @@ -1242,6 +1420,13 @@ nsresult nsExternalAppHandler::MoveFile(nsIFile * aNewFileLocation) { rv = mTempFile->MoveTo(directoryLocation, fileName); } + if (NS_FAILED(rv) && mWebProgressListener) + { + // Send error notification. + nsXPIDLString path; + fileToUse->GetUnicodePath(getter_Copies(path)); + SendStatusChange(kWriteError, rv, nsnull, mWebProgressListener, path.get()); + } } return rv; @@ -1282,7 +1467,7 @@ NS_IMETHODIMP nsExternalAppHandler::SaveToDisk(nsIFile * aNewFileLocation, PRBoo rv = PromptForSaveToFile(getter_AddRefs(fileToUse), mSuggestedFileName.get(), fileExt.get()); } - if (NS_FAILED(rv)) + if (NS_FAILED(rv) || !fileToUse) return Cancel(); mFinalFileDestination = do_QueryInterface(fileToUse); @@ -1318,6 +1503,13 @@ nsresult nsExternalAppHandler::OpenWithApplication(nsIFile * aApplication) if (helperAppService) { rv = helperAppService->LaunchAppWithTempFile(mMimeInfo, mFinalFileDestination); + if (NS_FAILED(rv) && mWebProgressListener) + { + // Send error notification. + nsXPIDLString path; + mFinalFileDestination->GetUnicodePath(getter_Copies(path)); + SendStatusChange(kLaunchError, rv, nsnull, mWebProgressListener,path.get()); + } #ifndef XP_MAC // Mac users have been very verbal about temp files being deleted on app exit - they @@ -1343,10 +1535,6 @@ NS_IMETHODIMP nsExternalAppHandler::LaunchWithApplication(nsIFile * aApplication mMimeInfo->SetPreferredAction(nsIMIMEInfo::useHelperApp); - // launch the progress window now that the user has picked the desired action. - if (!mProgressWindowCreated) - ShowProgressDialog(); - // user has chosen to launch using an application, fire any refresh tags now... ProcessAnyRefreshTags(); @@ -1384,6 +1572,10 @@ NS_IMETHODIMP nsExternalAppHandler::LaunchWithApplication(nsIFile * aApplication mFinalFileDestination = do_QueryInterface(fileToUse); + // launch the progress window now that the user has picked the desired action. + if (!mProgressWindowCreated) + ShowProgressDialog(); + return NS_OK; } diff --git a/uriloader/exthandler/nsExternalHelperAppService.h b/uriloader/exthandler/nsExternalHelperAppService.h index 58acd6ee38e..e1eec2f3793 100644 --- a/uriloader/exthandler/nsExternalHelperAppService.h +++ b/uriloader/exthandler/nsExternalHelperAppService.h @@ -150,7 +150,7 @@ struct nsDefaultMimeTypeEntry { #define DATA_BUFFER_SIZE (4096*2) class nsExternalAppHandler : public nsIStreamListener, public nsIHelperAppLauncher, public nsIURIContentListener, - public nsIInterfaceRequestor + public nsIInterfaceRequestor, public nsIObserver { public: NS_DECL_ISUPPORTS @@ -159,6 +159,7 @@ public: NS_DECL_NSIHELPERAPPLAUNCHER NS_DECL_NSIURICONTENTLISTENER NS_DECL_NSIINTERFACEREQUESTOR + NS_DECL_NSIOBSERVER nsExternalAppHandler(); virtual ~nsExternalAppHandler(); @@ -185,6 +186,8 @@ protected: PRBool mStopRequestIssued; PRBool mProgressWindowCreated; PRInt64 mTimeDownloadStarted; + PRInt32 mContentLength; + PRInt32 mProgress; // Number of bytes received (for sending progress notifications). // when we are told to save the temp file to disk (in a more permament location) before we are done // writing the content to a temp file, then we need to remember the final destination until we are ready to