Long lost patch for bugs 27609,35161, et al; r=mcafee, sr=mscott

This commit is contained in:
law%netscape.com 2001-02-07 04:33:39 +00:00
parent 6568dc534e
commit 5a7112f3dd
8 changed files with 195 additions and 139 deletions

View File

@ -350,6 +350,10 @@ nsBufferedOutputStream::Flush(void)
{
nsresult rv;
PRUint32 amt;
if (!mStream) {
// Stream already cancelled/flushed; probably because of error.
return NS_OK;
}
rv = Sink()->Write(mBuffer, mCursor, &amt);
if (NS_FAILED(rv)) return rv;
mBufferStartOffset += amt;

View File

@ -22,9 +22,9 @@
#include "nsISupports.idl"
#include "nsIChannel.idl"
#include "nsIFileSpec.idl"
interface nsIObserver;
interface nsILocalFile;
/*------------------------- nsIStreamTransferOperation -------------------------
| This interface is provided to enable the JavaScript portion of the standard |
@ -37,7 +37,7 @@ interface nsIObserver;
[scriptable, uuid(E2200F90-3E23-11d3-806A-00600811A9C3)]
interface nsIStreamTransferOperation : nsISupports {
readonly attribute nsIChannel source;
readonly attribute nsIFileSpec target;
readonly attribute nsILocalFile target;
attribute nsIObserver observer;
@ -56,4 +56,8 @@ interface nsIStreamTransferOperation : nsISupports {
const unsigned long kOpRead = 10;
const unsigned long kOpCreateFile = 11;
const unsigned long kOpAsyncRead = 12;
// Reason codes (for write errors):
const unsigned long kReasonAccessError = 1;
const unsigned long kReasonDiskFull = 2;
};

View File

@ -26,7 +26,8 @@ var dialog;
function loadDialog() {
dialog.location.setAttribute( "value", data.source.URI.spec );
dialog.fileName.setAttribute( "value", data.target.nativePath );
dialog.fileName.setAttribute( "value", data.target.unicodePath );
dialog.error = false;
}
var contractId = "@mozilla.org/appshell/component/xfer;1";
@ -59,24 +60,22 @@ var observer = {
}
function getString( stringId ) {
// Check if we've fetched this string already.
if ( !dialog.strings[ stringId ] ) {
// Try to get it.
var elem = document.getElementById( "dialog.strings."+stringId );
if ( elem
// Check if we've fetched this string already.
if ( !( stringId in dialog.strings ) ) {
// Try to get it.
var elem = document.getElementById( "dialog.strings."+stringId );
if ( elem
&&
elem.childNodes
elem.firstChild
&&
elem.childNodes[0]
&&
elem.childNodes[0].nodeValue ) {
dialog.strings[ stringId ] = elem.childNodes[0].nodeValue;
} else {
// If unable to fetch string, use an empty string.
dialog.strings[ stringId ] = "";
}
}
return dialog.strings[ stringId ];
elem.firstChild.nodeValue ) {
dialog.strings[ stringId ] = elem.firstChild.nodeValue;
} else {
// If unable to fetch string, use an empty string.
dialog.strings[ stringId ] = "";
}
}
return dialog.strings[ stringId ];
}
function replaceInsert( text, index, value ) {
@ -89,8 +88,8 @@ function replaceInsert( text, index, value ) {
function onLoad() {
// Set global variables.
data = window.arguments[0];
if ( !data ) {
dump( "Invalid argument to downloadProgress.xul\n" );
window.close()
return;
}
@ -111,17 +110,36 @@ function onLoad() {
// Commence transfer.
data.observer = observer;
data.Start();
try {
data.Start();
} catch( exception ) {
onError( exception );
}
}
function onUnload() {
// Unhook observer.
data.observer = null;
// Only do this one time...
if ( data ) {
// Unhook underlying xfer operation.
var op = data;
data = null;
// See if we completed normally (i.e., are closing ourself).
if ( !completed ) {
// Terminate transfer.
data.Stop();
// Unhook observer.
op.observer = null;
// See if we completed normally (i.e., are closing ourself).
if ( started && !completed ) {
// Terminate transfer.
try {
op.Stop();
} catch ( exception ) {
if ( !dialog.error ) {
// Show error now.
onError( exception );
}
}
op = null;
}
}
}
@ -141,7 +159,7 @@ var warningLimit = 30000; // Warn on Cancel after 30 sec (30000 milleseconds).
function stop() {
// If too much time has elapsed, make sure it's OK to quit.
if ( started ) {
if ( started && !completed ) {
// Get current time.
var now = ( new Date() ).getTime();
@ -155,7 +173,7 @@ function stop() {
}
// Stop the transfer.
data.Stop();
onUnload();
// Close the window.
window.close();
@ -321,11 +339,6 @@ function onCompletion( status ) {
// Close the window in 2 seconds (to ensure user sees we're done).
window.setTimeout( "window.close();", 2000 );
} catch ( exception ) {
dump( "Error setting close timeout\n" );
// OK, try to just close the window immediately.
window.close();
// If that's not working either, change button text to give user a clue.
dialog.cancel.childNodes[0].nodeValue = "Close";
}
}
@ -336,16 +349,56 @@ function onStatus( status ) {
if ( status.length > 9 ) {
dialog.status.setAttribute("value", status);
}
}
}
function onError( errorCode ) {
// XXX - l10n
var msg = "Unknown error";
switch ( errorCode ) {
default:
msg += " [" + errorCode + "]\n";
// Record fact we had an error.
dialog.error = true;
// Errorcode has format: "operation rv <string>" where
// operation - operation that failed; values are as defined
// in nsIStreamTransferOperation.idl
// rv - actual nsresult (in "%X" format)
// reason - reason for failure; values are as defined
// in nsIStreamTransferOperation.idl
var msg;
switch ( parseInt( errorCode.split(" ")[0] ) ) {
// Write errors...
case Components.interfaces.nsIStreamTransferOperation.kOpWrite :
case Components.interfaces.nsIStreamTransferOperation.kOpAsyncWrite :
case Components.interfaces.nsIStreamTransferOperation.kOpOpenOutputStream :
case Components.interfaces.nsIStreamTransferOperation.kOpCreateTransport :
case Components.interfaces.nsIStreamTransferOperation.kOpOutputClose :
case Components.interfaces.nsIStreamTransferOperation.kOpOutputCancel :
// Look for some specific reasons.
switch ( parseInt( errorCode.split(" ")[2] ) ) {
case Components.interfaces.nsIStreamTransferOperation.kReasonAccessError :
// Access/permission error.
msg = getString( "accessErrorMsg" );
break;
case Components.interfaces.nsIStreamTransferOperation.kReasonDiskFull :
// Out of space error.
msg = getString( "diskFullMsg" );
break;
default :
// Generic write error.
msg = getString( "writeErrorMsg" );
break;
}
break;
// Read errors...
default :
// Presume generic read error.
msg = getString( "readErrorMsg" );
break;
}
// Tell user.
alert( msg );
window.close();
// Dump error code to console.
dump( "downloadProgress.js onError: " + errorCode + "\n" );
// Terminate transfer and clean up.
stop();
}

View File

@ -45,10 +45,7 @@ Contributor(s):
<script src="chrome://global/content/downloadProgress.js"></script>
<!-- This is non-visible content that simply adds translatable string
into the document so that it is accessible to JS code.
XXX-TODO:
convert to use string bundles.
into the document so that it is accessible to JS code.
-->
<data id="dialog.strings.confirmCancel">&confirmCancel;</data>
@ -58,6 +55,10 @@ Contributor(s):
<data id="dialog.strings.shortTimeFormat">&shortTimeFormat;</data>
<data id="dialog.strings.longTimeFormat">&longTimeFormat;</data>
<data id="dialog.strings.unknownTime">&unknownTime;</data>
<data id="dialog.strings.diskFullMsg">&diskFullMsg;</data>
<data id="dialog.strings.accessErrorMsg">&accessErrorMsg;</data>
<data id="dialog.strings.writeErrorMsg">&writeErrorMsg;</data>
<data id="dialog.strings.readErrorMsg">&readErrorMsg;</data>
<grid flex="1">
<columns>
@ -116,4 +117,4 @@ Contributor(s):
<spring flex="1"/>
</box>
</window>
</window>

View File

@ -67,6 +67,12 @@
#1 will be replaced by the percentage of the file that has been saved -->
<!ENTITY percentMsg "#1&#037;">
<!-- Error messages -->
<!ENTITY diskFullMsg "There is not enough free space to store the file. Free up space and try again, or choose a different target location.">
<!ENTITY accessErrorMsg "You do not have permission to write to the target location, or, the disk is write-protected. Choose a different target location.">
<!ENTITY writeErrorMsg "There was an error writing to the target location.">
<!ENTITY readErrorMsg "There was an error reading from the source location.">
<!ENTITY keepProgressDialogUpMsg.label "Keep this window open after the download is complete.">
<!ENTITY openFolder.label "Reveal Location">
<!ENTITY open.label "Launch File">

View File

@ -70,7 +70,7 @@ protected:
private:
// Put up file picker dialog.
NS_IMETHOD SelectFile( nsIDOMWindowInternal *parent, nsIFileSpec **result, const nsCString &suggested );
NS_IMETHOD SelectFile( nsIDOMWindowInternal *parent, nsILocalFile **result, const nsCString &suggested );
nsCString SuggestNameFor( nsIChannel *aChannel, char const *suggestedName );
// Objects of this class are counted to manage library unloading...
@ -119,7 +119,7 @@ nsStreamTransfer::SelectFileAndTransferLocation( nsIChannel *aChannel,
char const *contentType,
char const *suggestedName ) {
// Prompt the user for the destination file.
nsCOMPtr<nsIFileSpec> outputFile;
nsCOMPtr<nsILocalFile> outputFile;
PRBool isValid = PR_FALSE;
nsresult rv = SelectFile( parent,
getter_AddRefs( outputFile ),
@ -127,11 +127,7 @@ nsStreamTransfer::SelectFileAndTransferLocation( nsIChannel *aChannel,
if ( NS_SUCCEEDED( rv )
&&
outputFile
&&
NS_SUCCEEDED( outputFile->IsValid( &isValid ) )
&&
isValid ) {
outputFile ) {
// Try to get HTTP channel.
nsCOMPtr<nsIHTTPChannel> httpChannel = do_QueryInterface( aChannel );
if ( httpChannel ) {
@ -218,7 +214,7 @@ nsStreamTransfer::SelectFileAndTransferLocationSpec( char const *aURL,
}
NS_IMETHODIMP
nsStreamTransfer::SelectFile( nsIDOMWindowInternal *parent, nsIFileSpec **aResult, const nsCString &suggested ) {
nsStreamTransfer::SelectFile( nsIDOMWindowInternal *parent, nsILocalFile **aResult, const nsCString &suggested ) {
nsresult rv = NS_OK;
if ( aResult ) {
@ -277,43 +273,19 @@ nsStreamTransfer::SelectFile( nsIDOMWindowInternal *parent, nsIFileSpec **aResul
rv = picker->Show( &rc );
}
#ifdef DEBUG_law
printf( "\nFile picker result = 0x%04X\n\n", (int)rc );
#endif
if ( rc != nsIFilePicker::returnCancel ) {
// Give result to caller.
nsCOMPtr<nsILocalFile> selection;
if ( NS_SUCCEEDED( picker->GetFile( getter_AddRefs( selection ) ) ) ) {
nsXPIDLCString selectionPath;
if ( NS_SUCCEEDED( selection->GetPath( getter_Copies( selectionPath ) ) ) ) {
rv = NS_NewFileSpec( aResult );
if ( NS_SUCCEEDED( rv ) ) {
rv = (*aResult)->SetNativePath( selectionPath );
printf( "\nresult native path = %s\n\n", (const char *)selectionPath );
}
}
}
rv = picker->GetFile( getter_AddRefs( aResult ) );
if ( NS_SUCCEEDED( rv ) && prefs ) {
// Save selected directory for next time.
nsCOMPtr<nsIFileSpec> startDirPath;
rv = (*aResult)->GetParent( getter_AddRefs(startDirPath));
nsCOMPtr<nsIFile> newStartDir;
rv = (*aResult)->GetParent( getter_AddRefs( newStartDir ) );
// go through nsFileSpec to get to nsILocalFile
nsFileSpec startDirSpec;
startDir = do_QueryInterface( newStartDir );
if (NS_SUCCEEDED(rv))
rv = startDirPath->GetFileSpec(&startDirSpec);
if (NS_SUCCEEDED(rv))
rv = NS_FileSpecToIFile(&startDirSpec,
getter_AddRefs(startDir));
if ( NS_SUCCEEDED( rv ) && startDir ) {
prefs->SetFileXPref( "browser.download.dir", startDir);
#ifdef DEBUG_law
printf( "\nbrowser.download.dir has been reset\n\n" );
#endif
prefs->SetFileXPref( "browser.download.dir", startDir );
}
}
} else if ( NS_SUCCEEDED( rv ) ) {

View File

@ -58,14 +58,15 @@ NS_IMPL_ISUPPORTS4(nsStreamXferOp, nsIStreamObserver, nsIStreamTransferOperation
#endif
// ctor - save arguments in data members.
nsStreamXferOp::nsStreamXferOp( nsIChannel *source, nsIFileSpec *target )
nsStreamXferOp::nsStreamXferOp( nsIChannel *source, nsILocalFile *target )
: mInputChannel( source ),
mOutputChannel( 0 ),
mOutputStream( 0 ),
mOutputSpec( target ),
mOutputFile( target ),
mObserver( 0 ),
mContentLength( 0 ),
mBytesProcessed( 0 ) {
mBytesProcessed( 0 ),
mError( PR_FALSE ) {
// Properly initialize refcnt.
NS_INIT_REFCNT();
}
@ -73,9 +74,6 @@ nsStreamXferOp::nsStreamXferOp( nsIChannel *source, nsIFileSpec *target )
// dtor
nsStreamXferOp::~nsStreamXferOp() {
// Delete dynamically allocated members (file and buffer).
#ifdef DEBUG_law
DEBUG_PRINTF( PR_STDOUT, "nsStreamXferOp destructor called\n" );
#endif
}
// Invoke nsIDOMWindowInternal::OpenDialog, passing this object as argument.
@ -134,14 +132,24 @@ NS_IMETHODIMP
nsStreamXferOp::OnError( int operation, nsresult errorCode ) {
nsresult rv = NS_OK;
#ifdef DEBUG_law
DEBUG_PRINTF( PR_STDOUT, "nsStreamXferOp::OnError; op=%d, rv=0x%08X\n",
operation, (int)errorCode );
#endif
// Record fact that an error has occurred.
mError = PR_TRUE;
if ( mObserver ) {
char buf[32];
PR_snprintf( buf, sizeof( buf ), "%d %X", operation, (int)errorCode );
// Pick off error codes of interest and convert to strings
// so JS code can test for them without hardcoded numbers.
PRUint32 reason = 0;
if ( errorCode == NS_ERROR_FILE_ACCESS_DENIED ) {
reason = kReasonAccessError;
} else if ( errorCode == NS_ERROR_FILE_READ_ONLY ) {
reason = kReasonAccessError;
} else if ( errorCode == NS_ERROR_FILE_NO_DEVICE_SPACE ) {
reason = kReasonDiskFull;
} else if ( errorCode == NS_ERROR_FILE_DISK_FULL ) {
reason = kReasonDiskFull;
}
char buf[64];
PR_snprintf( buf, sizeof( buf ), "%d %X %u", operation, (int)errorCode, reason );
rv = mObserver->Observe( (nsIStreamTransferOperation*)this,
NS_ConvertASCIItoUCS2( NS_ISTREAMTRANSFER_CONTRACTID ";onError" ).GetUnicode(),
NS_ConvertASCIItoUCS2( buf ).GetUnicode() );
@ -167,14 +175,10 @@ nsStreamXferOp::Start( void ) {
if ( NS_SUCCEEDED( rv ) ) {
// Next, create output file channel.
nsFileSpec target; // XXX eliminate
mOutputSpec->GetFileSpec( &target );
nsCOMPtr<nsILocalFile> file;
rv = NS_NewLocalFile(target, PR_FALSE, getter_AddRefs(file));
if (NS_SUCCEEDED(rv)) {
rv = fts->CreateTransport(file, PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE,
0664, getter_AddRefs( mOutputChannel));
}
rv = fts->CreateTransport( mOutputFile,
PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE,
0664,
getter_AddRefs( mOutputChannel ) );
if ( NS_SUCCEEDED( rv ) ) {
#ifdef USE_ASYNC_READ
@ -272,21 +276,17 @@ nsStreamXferOp::Stop( void ) {
// We also open the output stream at this point.
NS_IMETHODIMP
nsStreamXferOp::OnStartRequest(nsIChannel* channel, nsISupports* aContext) {
nsresult rv = NS_OK;
#ifdef DEBUG_law
DEBUG_PRINTF( PR_STDOUT, "nsStreamXferOp::OnStartRequest; channel=0x%08X, context=0x%08X\n",
(int)(void*)channel, (int)(void*)aContext );
#endif
nsresult rv = NS_ERROR_FAILURE;
#ifdef USE_ASYNC_READ
// Open output stream.
rv = mOutputChannel->OpenOutputStream( getter_AddRefs( mOutputStream ) );
if ( NS_FAILED( rv ) ) {
// Give up all hope.
this->OnError( kOpOpenOutputStream, rv );
this->Stop();
if ( !mOutputStream ) {
// Open output stream.
rv = mOutputChannel->OpenOutputStream( getter_AddRefs( mOutputStream ) );
if ( NS_FAILED( rv ) ) {
// Give up all hope.
this->OnError( kOpOpenOutputStream, rv );
this->Stop();
}
}
#endif // USE_ASYNC_READ
@ -307,27 +307,42 @@ nsStreamXferOp::OnDataAvailable( nsIChannel *channel,
// Write the data to the output stream.
// Read a buffer full till aLength bytes have been processed.
char buffer[ 8192 ];
unsigned long bytesRemaining = aLength;
while ( bytesRemaining ) {
unsigned int bytesRead;
char *bufPtr = buffer;
unsigned long bytesToRead = aLength;
while ( NS_SUCCEEDED( rv ) && bytesToRead ) {
// Write out remainder of read buffer.
unsigned int bytesToWrite;
// Read a buffer full or the number remaining (whichever is smaller).
rv = aIStream->Read( buffer,
PR_MIN( sizeof( buffer ), bytesRemaining ),
&bytesRead );
PR_MIN( sizeof( buffer ), bytesToRead ),
&bytesToWrite );
if ( NS_SUCCEEDED( rv ) ) {
// Write the bytes just read to the output stream.
unsigned int bytesWritten;
rv = mOutputStream->Write( buffer, bytesRead, &bytesWritten );
if ( NS_SUCCEEDED( rv ) && bytesWritten == bytesRead ) {
// All bytes written OK.
bytesRemaining -= bytesWritten;
} else {
// Something is wrong.
// Record bytes that have been read so far.
bytesToRead -= bytesToWrite;
// 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 = buffer; // Where to write from.
while ( NS_SUCCEEDED( rv ) && bytesToWrite ) {
unsigned int bytesWritten = 0;
rv = mOutputStream->Write( buffer, bytesToWrite, &bytesWritten );
if ( NS_SUCCEEDED( rv ) ) {
// Not all bytes were written for some strange reason.
rv = NS_ERROR_FAILURE;
// Adjust count of amount to write and the write location.
bytesToWrite -= bytesWritten;
bufPtr += bytesWritten;
// Force an error if (for some reason) we get NS_OK but
// no bytes written.
if ( !bytesWritten ) {
rv = NS_ERROR_FAILURE;
this->OnError( kOpWrite, rv );
}
} else {
// Something is wrong.
this->OnError( kOpWrite, rv );
}
this->OnError( kOpWrite, rv );
}
} else {
this->OnError( kOpRead, rv );
@ -434,7 +449,7 @@ nsStreamXferOp::OnStopRequest( nsIChannel *channel,
if ( NS_FAILED( aStatus ) ) {
this->Stop();
// XXX need to use aMsg when it is provided
this->OnError( kOpAsyncWrite, aStatus );
this->OnError( kOpAsyncRead, aStatus );
}
// Close the output stream.
@ -450,7 +465,7 @@ nsStreamXferOp::OnStopRequest( nsIChannel *channel,
mOutputChannel = 0;
// Notify observer that the download is complete.
if ( mObserver ) {
if ( !mError && mObserver ) {
nsString msg(aMsg);
rv = mObserver->Observe( (nsIStreamTransferOperation*)this,
NS_ConvertASCIItoUCS2( NS_ISTREAMTRANSFER_CONTRACTID ";onCompletion" ).GetUnicode(),
@ -481,11 +496,11 @@ nsStreamXferOp::GetSource( nsIChannel**result ) {
}
NS_IMETHODIMP
nsStreamXferOp::GetTarget( nsIFileSpec**result ) {
nsStreamXferOp::GetTarget( nsILocalFile**result ) {
nsresult rv = NS_OK;
if ( result ) {
*result = mOutputSpec;
*result = mOutputFile;
NS_IF_ADDREF( *result );
} else {
rv = NS_ERROR_NULL_POINTER;

View File

@ -28,7 +28,7 @@
#include "nsIStreamListener.h"
#include "nsIChannel.h"
#include "nsIOutputStream.h"
#include "nsIFileSpec.h"
#include "nsILocalFile.h"
#include "nsCOMPtr.h"
@ -56,7 +56,7 @@ class nsStreamXferOp : public nsIStreamTransferOperation,
#endif
public:
// ctor/dtor
nsStreamXferOp( nsIChannel *source, nsIFileSpec *target );
nsStreamXferOp( nsIChannel *source, nsILocalFile *target );
virtual ~nsStreamXferOp();
// Implementation.
@ -85,11 +85,12 @@ private:
nsCOMPtr<nsIChannel> mInputChannel;
nsCOMPtr<nsIChannel> mOutputChannel;
nsCOMPtr<nsIOutputStream> mOutputStream;
nsCOMPtr<nsIFileSpec> mOutputSpec;
nsCOMPtr<nsILocalFile> mOutputFile;
nsIObserver *mObserver; // Not owned; owner should call SetObserver(0) prior
// to this object getting destroyed.
int mContentLength;
unsigned long mBytesProcessed;
PRBool mError;
}; // nsStreamXferOp
#endif