Bug 27609; change exthandler to use new progress dialog and properly report i/o errors; r=bzbarsky, sr=mscott

This commit is contained in:
law%netscape.com 2002-02-20 08:00:29 +00:00
Родитель c292c09c27
Коммит 58689daf71
4 изменённых файлов: 222 добавлений и 23 удалений

Просмотреть файл

@ -49,8 +49,10 @@ REQUIRES = xpcom \
rdf \ rdf \
webshell \ webshell \
helperAppDlg \ helperAppDlg \
progressDlg \
plugin \ plugin \
pref \ pref \
intl \
$(NULL) $(NULL)
OSHELPER = nsOSHelperAppService.cpp OSHELPER = nsOSHelperAppService.cpp

Просмотреть файл

@ -29,8 +29,10 @@ REQUIRES = xpcom \
rdf \ rdf \
webshell \ webshell \
helperAppDlg \ helperAppDlg \
progressDlg \
plugin \ plugin \
unicharutil \ unicharutil \
intl \
$(NULL) $(NULL)
include <$(DEPTH)\config\config.mak> include <$(DEPTH)\config\config.mak>

Просмотреть файл

@ -53,6 +53,7 @@
#include "nsIRefreshURI.h" #include "nsIRefreshURI.h"
#include "nsIDocumentLoader.h" #include "nsIDocumentLoader.h"
#include "nsIHelperAppLauncherDialog.h" #include "nsIHelperAppLauncherDialog.h"
#include "nsIProgressDialog.h"
#include "nsITransport.h" #include "nsITransport.h"
#include "nsIFileTransportService.h" #include "nsIFileTransportService.h"
#include "nsCExternalHandlerService.h" // contains contractids for the helper app service #include "nsCExternalHandlerService.h" // contains contractids for the helper app service
@ -75,6 +76,8 @@
#include "nsIPluginHost.h" #include "nsIPluginHost.h"
#include "nsEscape.h" #include "nsEscape.h"
#include "nsIStringBundle.h"
const char *FORCE_ALWAYS_ASK_PREF = "browser.helperApps.alwaysAsk.force"; const char *FORCE_ALWAYS_ASK_PREF = "browser.helperApps.alwaysAsk.force";
static NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID); 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(nsIHelperAppLauncher)
NS_INTERFACE_MAP_ENTRY(nsIURIContentListener) NS_INTERFACE_MAP_ENTRY(nsIURIContentListener)
NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor) NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
NS_INTERFACE_MAP_ENTRY(nsIObserver)
NS_INTERFACE_MAP_END_THREADSAFE NS_INTERFACE_MAP_END_THREADSAFE
nsExternalAppHandler::nsExternalAppHandler() nsExternalAppHandler::nsExternalAppHandler()
@ -683,6 +687,8 @@ nsExternalAppHandler::nsExternalAppHandler()
mStopRequestIssued = PR_FALSE; mStopRequestIssued = PR_FALSE;
mDataBuffer = (char *) nsMemory::Alloc((sizeof(char) * DATA_BUFFER_SIZE)); mDataBuffer = (char *) nsMemory::Alloc((sizeof(char) * DATA_BUFFER_SIZE));
mProgressWindowCreated = PR_FALSE; mProgressWindowCreated = PR_FALSE;
mContentLength = -1;
mProgress = 0;
} }
nsExternalAppHandler::~nsExternalAppHandler() nsExternalAppHandler::~nsExternalAppHandler()
@ -697,6 +703,16 @@ NS_IMETHODIMP nsExternalAppHandler::GetInterface(const nsIID & aIID, void * *aIn
return QueryInterface(aIID, aInstancePtr); 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) NS_IMETHODIMP nsExternalAppHandler::SetWebProgressListener(nsIWebProgressListener * aWebProgressListener)
{ {
@ -725,7 +741,6 @@ NS_IMETHODIMP nsExternalAppHandler::SetWebProgressListener(nsIWebProgressListene
if (webProgress) if (webProgress)
{ {
mWebProgressListener = aWebProgressListener; mWebProgressListener = aWebProgressListener;
webProgress->AddProgressListener(mWebProgressListener);
} }
} }
return NS_OK; return NS_OK;
@ -1011,6 +1026,11 @@ NS_IMETHODIMP nsExternalAppHandler::OnStartRequest(nsIRequest *request, nsISuppo
nsresult rv = SetUpTempFile(aChannel); 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... // retarget all load notifcations to our docloader instead of the original window's docloader...
RetargetLoadNotifications(request); RetargetLoadNotifications(request);
@ -1023,6 +1043,8 @@ NS_IMETHODIMP nsExternalAppHandler::OnStartRequest(nsIRequest *request, nsISuppo
httpChannel->SetApplyConversion( PR_FALSE ); 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 // 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... // 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; 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<nsIStringBundleService> s = do_GetService(NS_STRINGBUNDLE_CONTRACTID);
if (s)
{
nsCOMPtr<nsIStringBundle> 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, NS_IMETHODIMP nsExternalAppHandler::OnDataAvailable(nsIRequest *request, nsISupports * aCtxt,
nsIInputStream * inStr, PRUint32 sourceOffset, PRUint32 count) nsIInputStream * inStr, PRUint32 sourceOffset, PRUint32 count)
{ {
nsresult rv = NS_OK;
// first, check to see if we've been canceled.... // first, check to see if we've been canceled....
if (mCanceled) // then go cancel our underlying channel too if (mCanceled) // then go cancel our underlying channel too
return request->Cancel(NS_BINDING_ABORTED); return request->Cancel(NS_BINDING_ABORTED);
@ -1076,17 +1155,73 @@ NS_IMETHODIMP nsExternalAppHandler::OnDataAvailable(nsIRequest *request, nsISupp
{ {
PRUint32 numBytesRead = 0; PRUint32 numBytesRead = 0;
PRUint32 numBytesWritten = 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); readError = PR_TRUE;
if (count >= numBytesRead) rv = inStr->Read(mDataBuffer, PR_MIN(count, DATA_BUFFER_SIZE - 1), &numBytesRead);
count -= numBytesRead; // subtract off the number of bytes we just read if (NS_SUCCEEDED(rv))
else {
count = 0; if (count >= numBytesRead)
mOutStream->Write(mDataBuffer, numBytesRead, &numBytesWritten); 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<nsIChannel> 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, NS_IMETHODIMP nsExternalAppHandler::OnStopRequest(nsIRequest *request, nsISupports *aCtxt,
@ -1110,6 +1245,13 @@ NS_IMETHODIMP nsExternalAppHandler::OnStopRequest(nsIRequest *request, nsISuppor
mOutStream = nsnull; 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(); 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... // 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; nsresult rv = NS_OK;
if ( !mDialog ) { nsCOMPtr<nsIProgressDialog> progressDlg = do_CreateInstance( "@mozilla.org/progressdialog;1", &rv );
// Get helper app launcher dialog. if (progressDlg)
mDialog = do_CreateInstance( NS_IHELPERAPPLAUNCHERDLG_CONTRACTID, &rv ); {
NS_ENSURE_SUCCESS(rv, rv); // Wire up this progress dialog.
} progressDlg->SetSource( mSourceUrl );
progressDlg->SetStartTime( mTimeDownloadStarted );
progressDlg->SetObserver(this);
nsCOMPtr<nsILocalFile> 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<nsIFile> appl;
mMimeInfo->GetPreferredApplicationHandler(getter_AddRefs(appl));
if (appl)
{
nsCOMPtr<nsILocalFile> 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; return rv;
} }
@ -1242,6 +1420,13 @@ nsresult nsExternalAppHandler::MoveFile(nsIFile * aNewFileLocation)
{ {
rv = mTempFile->MoveTo(directoryLocation, fileName); 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; return rv;
@ -1282,7 +1467,7 @@ NS_IMETHODIMP nsExternalAppHandler::SaveToDisk(nsIFile * aNewFileLocation, PRBoo
rv = PromptForSaveToFile(getter_AddRefs(fileToUse), mSuggestedFileName.get(), fileExt.get()); rv = PromptForSaveToFile(getter_AddRefs(fileToUse), mSuggestedFileName.get(), fileExt.get());
} }
if (NS_FAILED(rv)) if (NS_FAILED(rv) || !fileToUse)
return Cancel(); return Cancel();
mFinalFileDestination = do_QueryInterface(fileToUse); mFinalFileDestination = do_QueryInterface(fileToUse);
@ -1318,6 +1503,13 @@ nsresult nsExternalAppHandler::OpenWithApplication(nsIFile * aApplication)
if (helperAppService) if (helperAppService)
{ {
rv = helperAppService->LaunchAppWithTempFile(mMimeInfo, mFinalFileDestination); 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 #ifndef XP_MAC
// Mac users have been very verbal about temp files being deleted on app exit - they // 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); 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... // user has chosen to launch using an application, fire any refresh tags now...
ProcessAnyRefreshTags(); ProcessAnyRefreshTags();
@ -1384,6 +1572,10 @@ NS_IMETHODIMP nsExternalAppHandler::LaunchWithApplication(nsIFile * aApplication
mFinalFileDestination = do_QueryInterface(fileToUse); mFinalFileDestination = do_QueryInterface(fileToUse);
// launch the progress window now that the user has picked the desired action.
if (!mProgressWindowCreated)
ShowProgressDialog();
return NS_OK; return NS_OK;
} }

Просмотреть файл

@ -150,7 +150,7 @@ struct nsDefaultMimeTypeEntry {
#define DATA_BUFFER_SIZE (4096*2) #define DATA_BUFFER_SIZE (4096*2)
class nsExternalAppHandler : public nsIStreamListener, public nsIHelperAppLauncher, public nsIURIContentListener, class nsExternalAppHandler : public nsIStreamListener, public nsIHelperAppLauncher, public nsIURIContentListener,
public nsIInterfaceRequestor public nsIInterfaceRequestor, public nsIObserver
{ {
public: public:
NS_DECL_ISUPPORTS NS_DECL_ISUPPORTS
@ -159,6 +159,7 @@ public:
NS_DECL_NSIHELPERAPPLAUNCHER NS_DECL_NSIHELPERAPPLAUNCHER
NS_DECL_NSIURICONTENTLISTENER NS_DECL_NSIURICONTENTLISTENER
NS_DECL_NSIINTERFACEREQUESTOR NS_DECL_NSIINTERFACEREQUESTOR
NS_DECL_NSIOBSERVER
nsExternalAppHandler(); nsExternalAppHandler();
virtual ~nsExternalAppHandler(); virtual ~nsExternalAppHandler();
@ -185,6 +186,8 @@ protected:
PRBool mStopRequestIssued; PRBool mStopRequestIssued;
PRBool mProgressWindowCreated; PRBool mProgressWindowCreated;
PRInt64 mTimeDownloadStarted; 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 // 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 // writing the content to a temp file, then we need to remember the final destination until we are ready to