RetroZilla/extensions/sroaming/resources/content/transfer/transfer.js
2015-10-20 23:03:22 -04:00

1381 lines
46 KiB
JavaScript

/*-*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
/* ***** 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 Roaming code.
*
* The Initial Developer of the Original Code is
* Ben Bucksch <http://www.bucksch.org> of
* Beonex <http://www.beonex.com>
* Portions created by the Initial Developer are Copyright (C) 2002-2004
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Netscape Editor team, esp. Charles Manske <cmanske@netscape.com>
* ManyOne <http://www.manyone.net>
*
* 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 ***** */
/* Classes to transfer a list of files and to track the transfer progress.
It does not provide any UI.
You basically only need to create a Transfer object and call its transfer()
method. The callbacks will then be notified of the progress and possible
errors. In the callbacks, you can ask about the status (incl. errors)
of the files by reading the properties of the Transfer object
(see Transfer.dump() and TransferFile.dump() for a short explanation).
The functions in utility.js will help you with interpreting and
displaying possible errors.
The initial version/concept was stolen from Composer's publishing
functionality, but heavily modified (probably almost all lines changed). */
const nsIChannel = Components.interfaces.nsIChannel;
/*
Passes parameters for transfer.
@param download Bool download or upload
@param serial Bool true: transfer one file after the other
false: all files at once (parallel)
@param localDir String file:-URL of the local dir,
where the profile files are stored
@param remoteDir String URL of the dir on the server,
where the profile files are stored
@param password String Password for server in cleartext
@param savepw Bool Should password be stored by def. when asked?
@param files Array of Objects with properties
filename String path relative to
localDir/remoteDir, plus
filename (incl. possible
extension)
mimetype String undefined, if unknown
size int in bytes.
undefined, if unknown
@param progessCallback function(file) to be called when the progress
of a file transfer changed. can be used to e.g.
update the UI. parameter file is an entry in the
files array of this object, see makeFileVar()
and [this.]dump().
@param finishedCallback function(bool success) to be called when
we're done. |success|, if it seems that
all files have been transferred successfully
and completely.
@throws e.result==kMalformedURI bad base URI
@throws e.prop=="NoUsername" no username given
*/
function Transfer(download, serial,
localDir, remoteDir,
password, savepw,
files,
finishedCallback, progressCallback)
{
// from caller
this.download = download;
this.serial = serial;
this.localDir = localDir;
this.remoteDir = remoteDir;
var uri = GetIOService().newURI(remoteDir, null, null);
// this will throw an exception, if the user entered a bad URI.
// catch in caller
this.username = uri.username;
if (!this.username || this.username == "")
throw {prop:"NoUsername"};
this.password = password;
if (!savepw)
savepw = false;
this.savePassword = savepw; //savepw is when the user checked the checkbox
this.saveLogin = false; //savelogin is when we will need to write to disk
this.sitename = uri.host;
this.files = new Array();
for (var i = 0, l = files.length; i < l; i++)
{
this.files[i] = new TransferFile(this, i,
files[i].filename,
files[i].mimetype,
files[i].size);
}
// internal management
this.cancelled = false; // user pressed cancel. Don't proceed.
// progress
this.progressSizeAll = 0;
for (var i = 0, l = this.files.length; i < l; i++)
this.progressSizeAll += this.files[i].size;
// We fixed up this.files[].size, so files != this.files
this.progressSizeCompletedFiles = 0;
/* Cumulated size of the files which have been *completely* (not partially)
transferred. Used to speed up processing of incremental updates. */
// callbacks
this.finishedCallbacks = new Array();
this.progressCallbacks = new Array();
if (finishedCallback)
this.finishedCallbacks.push(finishedCallback);
if (progressCallback)
this.progressCallbacks.push(progressCallback);
/* I'm using arrays here, to allow the caller to hook up
additional callbacks. */
}
Transfer.prototype =
{
/* Called when all files finished
@param success bool no file failed
*/
done : function(success)
{
//ddump("Done(" + (success ? "without" : "with") + " errors)");
for (var i = 0, l = this.finishedCallbacks.length; i < l; i++)
this.finishedCallbacks[i](success);
/* The close dialog stuff (when all files are done successfully) is UI and
thus in sroamingProgress.js, called from SetProgressStatus(), which
should already be called. */
},
/* Initiate transfer of files
Note: when this function returns, the transfer is most likely *not*
finished yet.
@throws "transfer failed" (from transferFile)
*/
transfer : function()
{
//ddump("starting transfer");
this.dump();
if (this.files.length == 0)
this.done(true);
if (this.serial)
{
this.nextFile();
}
else
{
for (var i = 0, l = this.files.length; i < l; i++)
this.transferFile(i);
}
},
/* In serial mode, start the download of the next file, because all
previous downloads are finished.
@return 1: there are still files busy (something went wrong)
2: transfer of next file started
3: all files were already done, nothing left to do.
@throws "transfer failed" (from transferFile)
*/
nextFile : function()
{
// check that there are no downloads in progress
for (var i = 0, l = this.files.length; i < l; i++)
{
if (this.files[i].status == "busy")
{
dumpError("Programming error: nextFile() called, although there is "
+ "still a download in progress.");
this.dump();
//return 1;
}
}
// all halted?
if (this.cancelled)
return 3;
// find the next pending one
for (var i = 0, l = this.files.length; i < l; i++)
{
if (this.files[i].status == "pending")
{
this.transferFile(i);
return 2;
}
}
return 3;
},
/* Transfer that file.
Implements the actual upload/download.
Independent of serial/parallel (caller is responsible for that).
@param id index of this.files array of the file to be transferred
@throws "transfer failed"
*/
transferFile : function(id)
{
var file = this.files[id];
//ddump("TransferFile(" + id + ")");
try
{
var ioserv = GetIOService();
var baseURL = ioserv.newURI(this.remoteDir, null, null);
if (this.savePassword && this.password && this.password != "")
baseURL.password = this.password;
var localURL = ioserv.newURI(this.localDir, null, null);
var channel = ioserv.newChannel(file.filename, null, baseURL);
file.channel = channel;
var progress = new TransferProgressListener(this, id);
file.progressListener = progress;
channel.notificationCallbacks = progress;
//ddumpCont("Trying to "+ (this.download ? "download from" : "upload to"));
//ddumpCont(" remote URL <" + channel.URI.spec + "> ");
if (this.download)
{
var fos = Components
.classes["@mozilla.org/network/file-output-stream;1"]
.createInstance(Components.interfaces.nsIFileOutputStream);
var bos = Components
.classes["@mozilla.org/network/buffered-output-stream;1"]
.createInstance(Components.interfaces
.nsIBufferedOutputStream);
var ssl = Components
.classes["@mozilla.org/network/simple-stream-listener;1"]
.createInstance(Components.interfaces
.nsISimpleStreamListener);
/* Download to temporary local location first, in case something
goes wrong, e.g. failed auth (in that case, necko overwrites
the file with nothing! :-( ). Moved to final location in
fileFinished(). */
var lf = ioserv.newURI(file.filename + ".tmp", null, localURL)
.QueryInterface(Components.interfaces.nsIFileURL)
.file.clone();
file.localFile = lf;
//ddump("to local file " + lf.path);
try
{
fos.init(lf, -1, -1, 0);//odd params from NS_NewLocalFileOutputStream
}
catch(e)
{
var dir = lf.parent;
if (e.result == kFileNotFound
// we get this error, if the directory does no exist yet locally
&& dir && !dir.exists())
{
//ddumpCont("Creating dir " + dir.path + " ... ");
dir.create(Components.interfaces.nsIFile.DIRECTORY_TYPE, 0755);
// also creates all higher-level dirs, if necessary
//ddump("done (hopefully).");
fos.init(lf, -1, -1, 0); // try again
}
else
throw e; // pass on all other errors
}
bos.init(fos, 32768);
ssl.init(bos, progress);
file.fos = fos; // for flush after finished
file.bos = bos; // dito
channel.asyncOpen(ssl, null);
}
else // upload
{
var uploadChannel = channel.QueryInterface(
Components.interfaces.nsIUploadChannel);
var lfURL = ioserv.newURI(file.filename, null, localURL);
//ddump("from local file <" + lfURL.spec + ">");
var fileChannel = ioserv.newChannelFromURI(lfURL);
var is = fileChannel.open(); // maybe async? WFM.
var bis = Components
.classes["@mozilla.org/network/buffered-input-stream;1"]
.createInstance(Components.interfaces
.nsIBufferedInputStream);
bis.init(is, 32768);
uploadChannel.setUploadStream(bis, file.mimetype, -1);
/* I *must* use a mimetype here. Passing null will cause the
HTTP protocol implementation to use POST instead of PUT. */
channel.asyncOpen(progress, null);
}
}
catch(e)
{
if (e.result) // XPCOM error, we can report that to the user and we
// partially even expect it (file not found in new profiles etc.)
{
//ddump("Transfer problem, will later report it to user:\n" + e);
file.setStatus("failed", e.result, e.message);
}
else
{
//ddump("Unexpected (non-XPCOM) transfer problem:\n" + e);
file.setStatus("failed", kErrorUnexpected, e);
}
}
},
// called when an individual file transfer completed or failed
fileFinished : function(filenr)
{
//ddump("file " + filenr + " finished");
var file = this.files[filenr];
// flush/close - only for download
if (file.bos && file.fos)
{
file.bos.flush();
file.fos.flush();
file.bos.close();
file.fos.close();
}
// move to final location
if (this.download && file.localFile && file.localFile.exists())
{
if (file.status == "done")
file.localFile.moveTo(null, file.filename)
else
file.localFile.remove(false);
}
// check, if we're done with all files
var done = true;
var failed = false;
for (var i = 0, l = this.files.length; i < l; i++)
{
var file = this.files[i];
if (file.status == "failed")
failed = true; // |done| stays true
else if (file.status != "done") // "failed" already tested above
done = false;
}
if (done)
this.done(!failed);
else if (this.serial)
this.nextFile();
},
// user wishes transfer to stop
cancel : function()
{
//ddump("Canceling transfer");
this.cancelled = true; /* needed so that file.setStatus doesn't start
a next transfer in serial mode */
// network stuff
for (var i = 0, l = this.files.length; i < l; i++)
{
if (this.files[i].status != "done" &&
this.files[i].status != "failed")
{
this.files[i].setStatus("failed", kErrorAbort);
var channel = this.files[i].channel;
if (channel && channel.isPending)
{
channel.cancel(kErrorAbort);
/* the above line sets all files in the UI to failed, but that does
not close the dialog. */
}
}
}
/* finishedCallbacks called by done(), which will be called once all files
are finished/cancelled. */
},
/* Hack.
We need to change the list of files (add or remove files) after
the Transfer object was created. However, in that case, the internal
bookkeeping stuff (filenr of TransferFile) won't match anymore, so this
function fixes that.
Note: If you add files, create them using new TransferFile(). If you
remove files, do that best using splice or readd the remaining files to a
new array.
*/
filesChanged : function()
{
for (var i = 0, l = this.files.length; i < l; i++)
{
this.files[i].filenr = i;
}
},
// Progress
// functions dealing with tracking progress
// not sure, if that should be at the progress listener
progress : 0.0, // Gives the overall transfer progress of all files (0..1)
progressCalculate : true, // if false, don't update this.progress
/* The progress listener calls this function to notify it that the
transfer progress of a certain file changed.
This function will update the file's progress this.files[].progress
and the overall progress this.progress.
It will also call the owner's callbacks.
SLOW! :-(
@param filenr int index of the file whose progress changed
@param aProgress float 0..1 progress of that file
*/
progressChanged : function(filenr, aProgress)
{
if (!this.progressCalculate)
return;
//ddump("Transfer.progressChanged(" + filenr + ", " + aProgress + ")");
var file = this.files[filenr];
file.progress = aProgress;
if (this.progressSizeAll == 0) // undetermined mode
return;
// avoid too many updates
if (file.progress - file.progressPrevious < 0.01)
return;
else
file.progressPrevious = file.progress;
// calculate progress
var progressSize = 0;
if (file.status == "done")
{
//ddump(" file completed mode");
//ddump(" progressSizeAll: " + this.progressSizeAll);
//ddumpCont(" progressSizeCompletedFiles before: ");
//ddump(this.progressSizeCompletedFiles);
this.progressSizeCompletedFiles = 0;
for (var i = 0, l = this.files.length; i < l; i++)
{
var cur = this.files[i];
if (cur.status == "done")
this.progressSizeCompletedFiles += cur.size;
}
progressSize = this.progressSizeCompletedFiles;
//ddumpCont(" progressSizeCompletedFiles now: ");
//ddump(this.progressSizeCompletedFiles);
//ddump(" progressSize: " + progressSize);
}
else
{
/* It's probably just an update during the file download -
that happens often, so performance matters. */
if (this.serial)
{
//ddump(" update serial mode");
//ddump(" progressSizeAll: " + this.progressSizeAll);
//ddump(" progressSizeCompletedFiles: "+this.progressSizeCompletedFiles);
//ddump(" files[filenr].size: " + file.size);
//ddump(" files[filenr].progress: " + file.progress);
progressSize = this.progressSizeCompletedFiles
+ file.size * file.progress;
//ddump(" progressSize: " + progressSize);
}
else
{
//ddump(" update parallel mode");
//ddump(" progressSizeCompletedFiles: "+this.progressSizeCompletedFiles);
progressSize = this.progressSizeCompletedFiles;
for (var i = 0, l = this.files.length; i < l; i++)
{
var cur = this.files[i];
if (cur.status == "busy")
progressSize += cur.size * cur.progress;
}
//ddump(" progressSize: " + progressSize);
}
}
this.progress = progressSize / this.progressSizeAll;
// I protected at the very beginning against this.progressSizeAll == 0
for (var i = 0, l = this.progressCallbacks.length; i < l; i++)
this.progressCallbacks[i](filenr);
},
// functions dealing with conflict-(resolution), i.e. ensuring that
// we don't accidently overwrite newer files
/* Queries the last-modification-time and size of the roaming files,
from the OS and writes them all into a special local file.
This file will also be uploaded and later be used to compare the ...
*/
saveFileStats : function()
{
},
// Pass back info to dialog/c++.
/* returns int, really enum:
0 = do nothing, ignore all password/login changes
1 = Save password, set SavePW pref (user checked the box in the prompt)
2 = Set SavePassword pref to false and ignore Password/Username retval
*/
getSaveLogin : function()
{
if (this.saveLogin)
return this.savePassword ? 1 : 2;
else
return 0;
},
getPassword : function()
{
if (this.getSaveLogin() == 1)
return this.password;
else
return null;
},
getUsername : function()
{
if (this.getSaveLogin() == 1)
return this.username;
else
return null;
},
// helpful functions for callers
// returns an array of TransferFiles with all files with a certain status
filesWithStatus : function(status)
{
var result = new Array();
for (var i = 0, l = this.files.length; i < l; i++)
if (this.files[i].status == status)
result.push(this.files[i]);
return result;
},
// utility/debug
/*
Prints to the console the contents of this object.
*/
dump : function()
{
return;
//ddump("Transfer object:");
//ddump(" download: " + this.download);
// bool, see constructor
//ddump(" serial: " + this.serial);
// bool, see constructor
//ddump(" localDir: " + this.localDir);
// String, see constructor
//ddump(" remoteDir: " + this.remoteDir);
// String, see constructor
//ddump(" password: " + this.password);
// String, see constructor
//ddump(" savePassword: " + this.savePassword);
// bool, see constructor
//ddump(" sitename: " + this.sitename);
// String, to be displayed to the user identifying the remote server
//ddump(" progress: "+(this.progressCalculate ? this.progress : "disabled"));
// float, 0..1, how much of the files (measured by filesizes) has
// already been transfered
//ddump(" progressSizeAll: " + this.progressSizeAll);
// int, in bytes, cumulated filesizes as reported by the owner
//ddump(" Files: (" + this.files.length + " files)");
for (var i = 0, l = this.files.length; i < l; i++)
{
//ddump(" File " + i + ":");
this.files[i].dump();
}
//ddump(" finished callbacks: " + this.finishedCallbacks.length);
//ddump(" progress callbacks: " + this.progressCallbacks.length);
//for (var i = 0, l = this.finishedCallbacks.length; i < l; i++)
//dump(this.finishedCallbacks[i]);
}
}
/*
@param transfer Transfer the owner
@param filenr int index of this file in the transfer's files array
@param filename, mimetype, size see Transfer
*/
function TransferFile(transfer, filenr, // hooks to owner
filename, mimetype, size)
{
// sanitize input
if (mimetype == undefined || mimetype == "")
mimetype = "application/octet-stream";
if (size == undefined)
size = 0;
// for explanations, see this.dump()
this.filename = filename;
this.mimetype = mimetype;
this.size = size;
//ddump("got file " + filename + " with mimetype " + mimetype);
this.status = "pending";
this.statusCode = 0;
this.progress = 0.0;
this.channel = null;
this.progressListener = null;
this.fos = null;
this.bos = null;
this.localFile = null;
this.transfer = transfer;
this.filenr = filenr;
}
TransferFile.prototype =
{
dump : function()
{
//ddump(" filename: " + this.filename);
// String: relative pathname from profile root
//ddump(" mimetype: " + this.mimetype);
// String. might be undefined.
//ddump(" size: " + this.size);
// int, in bytes. might be 0 for undefined.
//ddump(" status: " + this.status);
// String: "pending" (not started), "busy", "done", "failed"
//ddumpCont(" statusCode: " + this.statusCode + " (");
//ddump(NameForStatusCode(this.statusCode) + ")");
// int: nsresult error code that we got from Necko
//ddump(" httpResponse: " + this.httpResponse);
// int: HTTP response code that we got from the server
//ddump(" statusText: " + this.statusText);
// String: Text corresponding to error (usually with httpResponse
// or fatal errors). Might be in English or the server's language.
// Text (usually translated) corresponding to statusCode can be
// gotten from ...utility.js; it may include this text.
//ddump(" progress: " + this.progress);
// float: 0 (nothing) .. 1 (complete)
},
/* Use this function to change this.status
@param aStatus String like this.status, i.e. "done", "pending" etc.
@param aStatusCode Mozilla error code
@param aMessage String Sets statusText, a long explanation specific
to this error case. Optional.
*/
setStatus : function(aStatus, aStatusCode, aMessage)
{
//ddumpCont("request to change "+this.filename+" from "+this.status+" (");
//ddumpCont(NameForStatusCode(this.statusCode)+") to "+aStatus+" (");
var undef = aStatusCode == undefined;
//ddump((undef ? "no new status code" : NameForStatusCode(aStatusCode))+")");
if (this.statusCode == aStatusCode && this.status == aStatus)
{
//ddump(" (no changes)");
return;
}
var was_failed = (this.status == "failed");
var was_done = (this.status == "done");
if (!was_failed) /* prevent overwriting older errors with newer, bogus
OK status codes or the real problem with
consequential problems. */
{
this.status = aStatus;
if (aStatusCode != undefined)
{
this.statusCode = aStatusCode;
//this.statusText = aMessage; // might be undefined, but it must match
// // this.statusCode
// WORKAROUND *sigh*, for FTP, we get the error msg before the error
}
if (aMessage)
this.statusText = aMessage;
for (var i = 0, l = this.transfer.progressCallbacks.length; i < l; i++)
this.transfer.progressCallbacks[i](this.filenr);
}
if (
!(was_done || was_failed)
&& (aStatus == "done" || aStatus == "failed")
) // file just completed or failed
this.transfer.fileFinished(this.filenr);
}
}
/* Use one object per file to be downloaded.
@param filenr integer index of file in transfer.files
@param transfer Transfer the context
*/
function TransferProgressListener(transfer, filenr)
{
this.transfer = transfer; // this creates a cyclic reference :-(
this.filenr = filenr;
this.file = this.transfer.files[this.filenr];
var consumer = Components.classes["@mozilla.org/binaryoutputstream;1"]
.createInstance(Components.interfaces
.nsIBinaryOutputStream);
/* I don't care about that stream, and it's empty anyways, so
just use any outputstream that stores in RAM.
nsStorageStream would probably have been more suited, but I don't
see how I could instantiate that from JS, and binaryoutputstream
works just as well. */
var buffer = Components
.classes["@mozilla.org/network/buffered-output-stream;1"]
.createInstance(Components.interfaces
.nsIBufferedOutputStream);
this.dummy_ssl = Components
.classes["@mozilla.org/network/simple-stream-listener;1"]
.createInstance(Components.interfaces
.nsISimpleStreamListener);
buffer.init(consumer, 32768);
this.dummy_ssl.init(buffer, null);
}
TransferProgressListener.prototype =
{
// Progress
// nsIRequestObserver
onStartRequest : function(aRequest, aContext)
{
//ddump("onStartRequest: " + aRequest.name);
this.file.setStatus("busy");
},
onStopRequest : function(aRequest, aContext, aStatusCode)
{
//ddump("onStopRequest:");
//ddump(" Request: " + aRequest.name);
//ddump(" StatusCode: " + NameForStatusCode(aStatusCode));
if (aStatusCode == kNS_OK)
{
/* WORKAROUND
HTTP gives us NS_OK, although the request failed with an HTTP error
code, so check for that.
FTP gives us NS_OK, although the transfer is still ongoing.
I tried to work around that in onStatus(), see comment there. */
var scheme = this.file.channel.URI.scheme;
if (scheme == "http")
this.privateHTTPResponse();
//else if (scheme == "ftp")
// return;
else
// let's hope that the other protocol impl.s are saner
this.file.setStatus("done", aStatusCode);
}
else
this.privateStatusChange(aStatusCode);
},
// nsIProgressEventSink
onStatus : function(aRequest, aContext, aStatusCode, aStatusArg)
{
/* WORKAROUND
The status codes that are passed to us here in aStatusCode look like
nsresult error codes, but their numerical values overlap with other,
real errors. Darin said that real errors are never passed in here,
so we don't have a hard conflict. To make processing easier, I just
translate these status codes into made-up, unique error codes,
so we can later check them together with other status and error codes
and store them in the normal this.status field (which contains
normal XPCOM error codes) etc.. */
if (aStatusCode == kStatusResolvingHost_Status)
aStatusCode = kStatusResolvingHost;
else if (aStatusCode == kStatusConnectedTo_Status)
aStatusCode = kStatusConnectedTo;
else if (aStatusCode == kStatusSendingTo_Status)
aStatusCode = kStatusSendingTo;
else if (aStatusCode == kStatusReceivingFrom_Status)
aStatusCode = kStatusReceivingFrom;
else if (aStatusCode == kStatusConnectingTo_Status)
aStatusCode = kStatusConnectingTo;
else if (aStatusCode == kStatusWaitingFor_Status)
aStatusCode = kStatusWaitingFor;
else if (aStatusCode == kStatusReadFrom_Status)
aStatusCode = kStatusReadFrom;
else if (aStatusCode == kStatusWroteTo_Status)
aStatusCode = kStatusWroteTo;
//ddump("onStatus:");
//ddump(" Request: " + aRequest.name);
//ddump(" StatusCode: " + NameForStatusCode(aStatusCode));
//ddump(" StatusArg: " + aStatusArg);
/* WORKAROUND
We sometimes get onStopRequest *before* e.g. onStatus(SENDING_TO), so
ignore all status msgs after onstop. This is dangerous, because
IIRC I saw onStop also on the very beginning of a transfer, and
if that happens for the last file, we might close the dialog before
the transfer finished, but I don't know another quick and not too bad
workaround. */
if (this.file.status == "done")
return;
this.privateStatusChange(aStatusCode);
},
/* Use this function to interpret Mozilla status/error codes and
update |this| appropriately.
Don't use it for NS_OK - what this means depends on the situation and
thus can't be interpreted here.
@param aStatusCode Mozilla error code
*/
privateStatusChange : function(aStatusCode)
{
var status;
if (aStatusCode == kNS_OK)
// don't know what this means, only the caller knows what succeeded
return;
else if (aStatusCode == kStatusReadFrom)
status = "busy";
else if (aStatusCode == kStatusReceivingFrom)
status = "busy";
else if (aStatusCode == kStatusSendingTo)
status = "busy";
else if (aStatusCode == kStatusWaitingFor)
status = "busy";
else if (aStatusCode == kStatusResolvingHost)
status = "busy";
else if (aStatusCode == kStatusConnectedTo)
status = "busy";
else if (aStatusCode == kStatusConnectingTo)
status = "busy";
else if (aStatusCode == kErrorBindingRedirected)
status = "busy";
else if (aStatusCode == kErrorBindingRetargeted)
status = "busy";
else if (aStatusCode == kStatusBeginFTPTransaction)
status = "busy";
else if (aStatusCode == kStatusEndFTPTransaction)
status = "done";
else if (aStatusCode == kInProgress)
status = "busy";
else if (aStatusCode == kStatusFTPLogin)
status = "busy";
else if (aStatusCode == kErrorFTPAuthNeeded)
status = "busy";
else if (aStatusCode == kNetReset)
; // correct?
else
status = "failed";
this.file.setStatus(status, aStatusCode);
},
/* Use this function to get and interpret HTTP status/error codes and
update |this| appropriately.
*/
privateHTTPResponse : function()
{
//ddumpCont("privateHTTPResponse ");
// Get HTTP response code
var responseCode;
var respText;
try
{
var httpchannel = this.file.channel
.QueryInterface(Components.interfaces.nsIHttpChannel);
responseCode = httpchannel.responseStatus;
this.file.httpResponse = responseCode;
respText = httpchannel.responseStatusText;
//ddump(responseCode);
}
catch(e)
{
//ddump(" Error while trying to get HTTP response: " + e);
this.file.setStatus("failed", kErrorNoInterface);
return;
}
// Interpret HTTP response code
var status;
if (responseCode >= 100 && responseCode < 200)
status = "busy";
else if (responseCode >= 200 && responseCode < 300)
status = "done";
else if (responseCode >= 300 && responseCode < 400)
status = "busy";
else if (responseCode >= 400 && responseCode < 600)
status = "failed";
else // what?
return;
this.file.setStatus(status, kStatusHTTP, respText);
},
onProgress : function(aRequest, aContext, aProgress, aProgressMax)
{
//ddump("onProgress: " + aProgress + "/" + aProgressMax);
if (aProgressMax > 0 && aProgress > 0)
// WORKAROUND Necko sometimes sends crap like 397/0 or 0/4294967295
{
this.transfer.progressChanged(this.filenr, aProgress / aProgressMax);
}
},
// Dummies
// nsStreamListener
onDataAvailable : function(aRequest, aContext,
anInputStream, anOffset, aCount)
{
/* dummy, because it's only the down stream during an upload
and during download, we use the tee, which copies to the outstream. */
this.dummy_ssl.onDataAvailable(aRequest, aContext, anInputStream,
anOffset, aCount);
},
// nsIFTPEventSink, nsIHTTPEventSink
OnFTPControlLog : function(aServer, aMsg)
{
// dummy
},
onChannelRedirect : function(aOldChannel, aNewChannel, aFlags)
{
// dummy
},
/*
// nsIDocShellTreeItem (wtf?)
name : "dummy in transfer.js",
itemType : 0,
parent : null,
sameTypeParent : null,
rootTreeItem : null,
sameTypeRootTreeItem : null,
treeOwner : null,
childOffset : 0,
nameEquals : function(name)
{
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
},
findItemWithName : function(name, requestor)
{
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
},
*/
// Prompts
// nsIPrompt
alert : function(dlgTitle, text)
{
//ddump("alert " + text);
/* WORKAROUND
FTP sends us these in the case of an error *sigh*. Don't display dialog,
but redirect to file errors. */
this.file.statusText = text.replace(/\n/, "");
/*
if (!dlgTitle)
title = GetString("Alert");
GetPromptService().alert(window, dlgTitle, text);
*/
},
alertCheck : function(dlgTitle, text, checkBoxLabel, checkObj)
{
//ddump("alertCheck");
this.alert(dlgTitle, text);
},
confirm : function(dlgTitle, text)
{
//ddump("confirm");
return this.confirmCheck(dlgTitle, text, null, null);
},
confirmCheck : function(dlgTitle, text, checkBoxLabel, checkObj)
{
//ddump("confirmCheck");
var promptServ = GetPromptService();
if (!promptServ)
return;
promptServ.confirmEx(window, dlgTitle, text,
nsIPromptService.STD_OK_CANCEL_BUTTONS,
"", "", "", checkBoxLabel, checkObj);
},
confirmEx : function(dlgTitle, text, btnFlags,
btn0Title, btn1Title, btn2Title,
checkBoxLabel, checkVal)
{
//ddump("confirmEx");
var promptServ = GetPromptService();
if (!promptServ)
return 0;
return promptServ.confirmEx(window, dlgTitle, text, btnFlags,
btn0Title, btn1Title, btn2Title,
checkBoxLabel, checkVal);
},
select : function(dlgTitle, text, count, selectList, outSelection)
{
//ddump("select");
var promptServ = GetPromptService();
if (!promptServ)
return false;
return promptServ.select(window, dlgTitle, text, count,
selectList, outSelection);
},
prompt : function(dlgTitle, label, /*inout*/ inputvalueObj,
checkBoxLabel, /*inout*/ checkObj)
{
//ddump("nsIPrompt::prompt");
return this.privatePrompt(dlgTitle, label, null, inputvalueObj,
checkBoxLabel, checkObj);
},
/* Warning: these |promptPassword| and |promptUsernameAndPassword|
from nsIPrompt will never be invoked. If they are being called, the
nsIAuthPrompt versions will be invoked, with wrong arguments.
This is because we're overloading the functions with the same number
of parameters and JS can't distinguish between different parameter types.
This is a bug copied from Editor's Publishing feature; the bug exists
there as well. */
promptPassword : function(dlgTitle, label, /*inout*/ pwObj,
checkBoxLabel, /*inout*/ savePWObj)
{
//ddump("nsIPrompt::promptPassword:");
//ddump(" pwObj: " + pwObj.value);
//ddump(" checkBoxLabel: " + checkBoxLabel);
//ddump(" savePWObj: " + savePWObj.value + " (ignored)");
var ret = this.privatePromptPassword(dlgTitle, label, null,
pwObj, checkBoxLabel);
savePWObj.value = this.transfer.savePassword; // out param
return ret;
},
promptUsernameAndPassword : function(dlgTitle, label,
/*inout*/ userObj, /*inout*/ pwObj,
savePWLabel, /*inout*/ savePWObj)
{
//ddump("nsIPrompt::promptUsernameAndPassword:");
//ddump(" userObj: " + userObj.value);
//ddump(" pwObj: " + pwObj.value);
//ddump(" savePWLabel: " + savePWLabel);
//ddump(" savePWObj: " + savePWObj.value + " (ignored)");
var ret = this.privatePromptUsernameAndPassword(dlgTitle, label, null,
userObj, pwObj,
savePWLabel);
savePWObj.value = this.transfer.savePassword; // out param
return ret;
},
// nsIAuthPrompt
prompt : function(dlgTitle, label, pwrealm, /*in*/ savePW, defaultText,
/*out*/ resultObj)
{
//ddump("nsIAuthPrompt::prompt");
var savePWObj = {value:this.transfer.savePassword};
// I know it better than the caller.
var inputvalueObj = {value:defaultText};
var ret = this.privatePrompt(dlgTitle, label, pwrealm,
inputvalueObj, null, savePWObj);
resultObj.value = inputvalueObj.value;
return ret;
},
promptPassword : function(dlgTitle, label, pwrealm, /*in*/ savePW,
/*out*/ pwObj)
{
//ddump("nsIAuthPrompt::promptPassword");
//ddump(" pwrealm: " + pwrealm);
//ddump(" savePW: " + savePW + " (ignored)");
//ddump(" pwObj: " + pwObj.value);
return this.privatePromptPassword(dlgTitle, label, pwrealm,
pwObj, null);
},
promptUsernameAndPassword : function(dlgTitle, label, pwrealm, /*in*/savePW,
/*out*/ userObj, /*out*/ pwObj)
{
//ddump("nsIAuthPrompt::promptUsernameAndPassword:");
//ddump(" pwrealm: " + pwrealm);
//ddump(" savePW: " + savePW + " (ignored)");
//ddump(" userObj: " + userObj.value);
//ddump(" pwObj: " + pwObj.value);
return this.privatePromptUsernameAndPassword(dlgTitle, label, pwrealm,
userObj, pwObj, null);
},
// helper functions for prompt stuff
privatePrompt : function(dlgTitle, label, pwrealm, /*inout*/ inputvalueObj,
checkBoxLabel, /*inout*/ checkObj)
{
if (!dlgTitle)
dlgTitle = GetString("PasswordTitle");
if (!pwrealm)
pwrealm = this.transfer.sitename;
if (!label)
label = this.makePWLabel(userObj.value, pwrealm);
if (!checkBoxLabel)
checkBoxLabel = GetString("SavePassword");
var promptServ = GetPromptService();
if (!promptServ)
return false;
var ret = promptServ.prompt(window, dlgTitle, label,
inputvalueObj, checkBoxLabel, checkObj);
if (!ret)
this.transfer.cancel();
return ret;
},
privatePromptPassword : function(dlgTitle, label, pwrealm,
/*inout*/ pwObj, savePWLabel)
{
//ddump("privatePromptPassword()");
//ddump(" pwObj.value: " + pwObj.value);
//ddump(" savePWLabel: " + savePWLabel);
//ddump(" label: " + label);
//ddump(" pwrealm: " + pwrealm);
//ddump(" this.transfer.savePassword: " + this.transfer.savePassword);
//ddump(" this.transfer.username: " + this.transfer.username);
if (!dlgTitle)
dlgTitle = GetString("PasswordTitle");
if (!pwrealm)
pwrealm = this.transfer.sitename;
if (!label)
label = this.makePWLabel(userObj.value, pwrealm);
if (!savePWLabel)
savePWLabel = GetString("SavePassword");
var savePWObj = {value: this.transfer.savePassword};
//ddump(" pwObj.value: " + pwObj.value);
//ddump(" savePWLabel: " + savePWLabel);
//ddump(" label: " + label);
//ddump(" pwrealm: " + pwrealm);
var promptServ = GetPromptService();
if (!promptServ)
return false;
var ret = false;
try {
ret = promptServ.promptPassword(window, dlgTitle, label,
pwObj, savePWLabel, savePWObj);
if (!ret)
this.transfer.cancel();
else
this.updateUsernamePasswordFromPrompt(this.transfer.username,
pwObj.value, savePWObj.value);
} catch (e) {
dumpError(e);
}
return ret;
},
privatePromptUsernameAndPassword : function(dlgTitle, label, pwrealm,
/*inout*/ userObj, /*inout*/ pwObj,
savePWLabel)
{
// HTTP prompts us twice even if user Cancels from 1st attempt!
//ddump("privatePromptUsernameAndPassword()");
//ddump(" pwObj.value: " + pwObj.value);
//ddump(" userObj.value: " + userObj.value);
//ddump(" savePWLabel: " + savePWLabel);
//ddump(" label: " + label);
//ddump(" pwrealm: " + pwrealm);
//ddump(" this.transfer.savePassword: " + this.transfer.savePassword);
//ddump(" this.transfer.username: " + this.transfer.username);
if (!dlgTitle)
dlgTitle = GetString("PasswordTitle");
if (!pwrealm)
pwrealm = this.transfer.sitename;
if (!label)
label = this.makePWLabel(userObj.value, pwrealm);
if (!savePWLabel)
savePWLabel = GetString("SavePassword");
if (!userObj.value)
// HTTP PUT uses this dialog if either username or password is bad,
// so prefill username with the previous value
userObj.value = this.transfer.username;
var savePWObj = {value: this.transfer.savePassword};
//ddump(" savePWObj.value: " + savePWObj.value);
//ddump(" userObj.value: " + userObj.value);
//ddump(" savePWLabel: " + savePWLabel);
//ddump(" label: " + label);
//ddump(" pwrealm: " + pwrealm);
/* I had the idea to put up the password-only dialog, if we have a
username. But if the user didn't enter a username in the prefs,
it first all works correctly, the username+password dialog comes up.
The problem starts when the user entered the wrong username -
he won't have a chance to change it anymore. Not sure, if that is bad.
So, the code for that is below.
Update: I decided that the user has to enter a username in any case,
and he can't change it during login. At least not in this case. */
if (userObj.value && userObj.value != "")
{
// |label| here says "enter username and password ...", so we need
// to build our own.
label = this.makePWLabel(userObj.value, pwrealm);
return this.privatePromptPassword(dlgTitle, label, pwrealm,
pwObj, null);
}
// We should get here only, if we got an empty username pref from the reg.
//ddump(" asking for username as well");
var ret = false;
try {
var promptServ = GetPromptService();
if (!promptServ)
return false;
ret = promptServ.promptUsernameAndPassword(window,
dlgTitle, label, userObj, pwObj,
savePWLabel, savePWObj);
if (!ret)
this.transfer.cancel();
else
this.updateUsernamePasswordFromPrompt(userObj.value, pwObj.value,
savePWObj.value);
} catch (e) {
dumpError(e);
}
//ddump(" done. result:");
//ddump(" userObj.value: " + userObj.value);
//ddump(" pwObj.value: " + pwObj.value);
//ddump(" savePWObj.value: " + savePWObj.value);
return ret;
},
// Update any data that the user supplied in a prompt dialog
updateUsernamePasswordFromPrompt : function(username, password,savePassword)
{
// Set flag to save login data after transferring, if it changed in
// dialog and the "SavePassword" checkbox was checked
this.transfer.saveLogin = (username != this.transfer.username ||
password != this.transfer.password)
&& savePassword;
//ddump(" username: " + username);
//ddump(" password: " + password);
//ddump(" savePW: " + savePassword);
//ddump(" saveLogin: " + this.transfer.saveLogin);
this.transfer.username = username;
this.transfer.password = password;
this.transfer.savePassword = savePassword;
var uri = GetIOService().newURI(this.transfer.remoteDir, null, null);
uri.username = username;
this.transfer.remoteDir = uri.spec; // doesn't work, the spec isn't updated
this.transfer.dump();
},
makePWLabel : function (user, realm)
{
if (!user)
user = "";
var label = GetString("EnterPasswordForUserAt");
label = label.replace(/%username%/, user);
label = label.replace(/%realm%/, realm);
return label;
},
// XPCOM
// nsISupports
QueryInterface : function(aIID)
{
// ddump("QI: " + aIID.toString() + "\n");
if (aIID.equals(Components.interfaces.nsIProgressEventSink)
|| aIID.equals(Components.interfaces.nsIRequestObserver)
|| aIID.equals(Components.interfaces.nsIStreamListener)
|| aIID.equals(Components.interfaces.nsISupports)
|| aIID.equals(Components.interfaces.nsISupportsWeakReference)
|| aIID.equals(Components.interfaces.nsIPrompt)
|| aIID.equals(Components.interfaces.nsIAuthPrompt)
|| aIID.equals(Components.interfaces.nsIFTPEventSink)
|| aIID.equals(Components.interfaces.nsIChannelEventSink)
|| aIID.equals(Components.interfaces.nsIDocShellTreeItem)
|| aIID.equals(Components.interfaces.nsIInterfaceRequestor))
return this;
throw Components.results.NS_NOINTERFACE;
},
// nsIInterfaceRequestor
getInterface : function(aIID)
{
return this.QueryInterface(aIID);
}
}
var gIOService;
function GetIOService()
{
if (gIOService)
return gIOService;
// throw errors into caller
gIOService = Components.classes["@mozilla.org/network/io-service;1"]
.getService(Components.interfaces.nsIIOService);
return gIOService;
}