////////////////////////////////
var keepProxy;
+ var defaultErrorMessage = 'A network error occurred: either the server was unreachable, or there is a server configuration problem. Please check your browser debug console for a more specific error message (browser security features prevent us from showing the details here).';
function SliceReader(_slice) {
var that = this;
// resolve(locator) when the block is accepted by the
// proxy.
_deferred = $.Deferred();
- goSend();
+ if (proxyUriBase().match(/^http:/) &&
+ window.location.origin.match(/^https:/)) {
+ // In this case, requests will fail, and no ajax
+ // success/fail handlers will be called (!), which
+ // will leave our status saying "uploading" and the
+ // user waiting for something to happen. Better to
+ // give up now.
+ _deferred.reject({
+ textStatus: 'error',
+ err: 'There is a server configuration problem. Proxy ' + proxyUriBase() + ' cannot be used from origin ' + window.location.origin + ' due to the browser\'s mixed-content (https/http) policy.'
+ });
+ } else {
+ goSend();
+ }
return _deferred.promise();
}
function stop() {
function FileUploader(file) {
$.extend(this, {
- committed: false,
file: file,
locators: [],
progress: 0.0,
- state: 'Queued', // Queued, Uploading, Paused, Done
+ state: 'Queued', // Queued, Uploading, Paused, Uploaded, Done
statistics: null,
go: go,
stop: stop // User wants to stop.
function go() {
if (_deferred)
_deferred.reject({textStatus: 'restarted'});
- _deferred = $q.defer();
+ _deferred = $.Deferred();
that.state = 'Uploading';
_startTime = Date.now();
_startByte = _readPos;
setProgress();
goSlice();
- return _deferred.promise;
+ return _deferred.promise().always(function() { _deferred = null; });
}
function stop() {
if (_deferred) {
// here is fulfilled.
_currentSlice = nextSlice();
if (!_currentSlice) {
- that.state = 'Done';
+ // All slices have been uploaded, but the work won't
+ // be truly Done until the target collection has been
+ // updated by the QueueUploader. This state is called:
+ that.state = 'Uploaded';
setProgress(_readPos);
_currentUploader = null;
_deferred.resolve([that]);
that.state = 'Paused';
setProgress(_readPos);
_currentUploader = null;
- _deferred.reject(reason);
+ if (_deferred)
+ _deferred.reject(reason);
}
function onUploaderProgress(sliceDone, sliceSize) {
setProgress(_readPos + sliceDone);
}
function setProgress(bytesDone) {
var kBps;
- that.progress = Math.min(100, 100 * bytesDone / that.file.size)
+ if (that.file.size == 0)
+ that.progress = 100;
+ else
+ that.progress = Math.min(100, 100 * bytesDone / that.file.size);
if (bytesDone > _startByte) {
kBps = (bytesDone - _startByte) /
(Date.now() - _startTime);
new Date(
Date.now() + (that.file.size - bytesDone) / kBps),
'shortTime')
- } else {
- that.statistics += ', finished ' +
- $filter('date')(Date.now(), 'shortTime');
- _finishTime = Date.now();
}
} else {
that.statistics = that.state;
}
- _deferred.notify();
+ if (that.state === 'Uploaded') {
+ // 'Uploaded' gets reported as 'finished', which is a
+ // little misleading because the collection hasn't
+ // been updated yet. But FileUploader's portion of the
+ // work (and the time when it makes sense to show
+ // speed and ETA) is finished.
+ that.statistics += ', finished ' +
+ $filter('date')(Date.now(), 'shortTime');
+ _finishTime = Date.now();
+ }
+ if (_deferred)
+ _deferred.notify();
}
}
function QueueUploader() {
$.extend(this, {
- state: 'Idle',
+ state: 'Idle', // Idle, Running, Stopped, Failed
stateReason: null,
statusSuccess: null,
go: go,
});
////////////////////////////////
var that = this;
- var _deferred;
+ var _deferred; // the one we promise to go()'s caller
+ var _deferredAppend; // tracks current appendToCollection
function go() {
- if (that.state === 'Running') return _deferred.promise;
+ if (_deferred) return _deferred.promise();
+ if (_deferredAppend) return _deferredAppend.promise();
_deferred = $.Deferred();
that.state = 'Running';
ArvadosClient.apiPromise(
{filters: [['service_type','=','proxy']]}).
then(doQueueWithProxy);
onQueueProgress();
- return _deferred.promise();
+ return _deferred.promise().always(function() { _deferred = null; });
}
function stop() {
that.state = 'Stopped';
+ if (_deferred) {
+ _deferred.reject({});
+ }
for (var i=0; i<$scope.uploadQueue.length; i++)
$scope.uploadQueue[i].stop();
+ onQueueProgress();
}
function doQueueWithProxy(data) {
keepProxy = data.items[0];
return doQueueWork();
}
function doQueueWork() {
- that.state = 'Running';
- that.stateReason = null;
// If anything is not Done, do it.
if ($scope.uploadQueue.length > 0 &&
$scope.uploadQueue[0].state !== 'Done') {
- return $scope.uploadQueue[0].go().
- then(appendToCollection, null, onQueueProgress).
- then(doQueueWork, onQueueReject);
+ if (_deferred) {
+ that.stateReason = null;
+ return $scope.uploadQueue[0].go().
+ then(appendToCollection, null, onQueueProgress).
+ then(doQueueWork, onQueueReject);
+ } else {
+ // Queue work has been stopped. Just update the
+ // view.
+ onQueueProgress();
+ return;
+ }
}
- // If everything is Done, resolve the promise and clean up.
- return onQueueResolve();
+ // If everything is Done, resolve the promise and clean
+ // up. Note this can happen even after the _deferred
+ // promise has been rejected: specifically, when stop() is
+ // called too late to prevent completion of the last
+ // upload. In that case we want to update state to "Idle",
+ // rather than leave it at "Stopped".
+ onQueueResolve();
}
function onQueueReject(reason) {
- if (that.state !== 'Stopped') {
- that.state = 'Error';
+ if (!_deferred) {
+ // Outcome has already been decided (by stop()).
+ return;
}
- // (else it's not really an error, just a consequence of stop())
+ that.state = 'Failed';
that.stateReason = (
(reason.textStatus || 'Error') +
(reason.xhr && reason.xhr.options
? (' (from ' + reason.xhr.options.url + ')')
: '') +
': ' +
- (reason.err || ''));
+ (reason.err || defaultErrorMessage));
if (reason.xhr && reason.xhr.responseText)
that.stateReason += ' -- ' + reason.xhr.responseText;
_deferred.reject(reason);
function onQueueResolve() {
that.state = 'Idle';
that.stateReason = 'Done!';
- _deferred.resolve();
+ if (_deferred)
+ _deferred.resolve();
onQueueProgress();
}
function onQueueProgress() {
$timeout(function(){$scope.$apply();});
}
function appendToCollection(uploads) {
- var deferred = $q.defer();
- return ArvadosClient.apiPromise(
+ _deferredAppend = $.Deferred();
+ ArvadosClient.apiPromise(
'collections', 'get',
{ uuid: $scope.uuid }).
then(function(collection) {
var manifestText = '';
$.each(uploads, function(_, upload) {
+ var locators = upload.locators;
+ if (locators.length === 0) {
+ // Every stream must have at least one
+ // data locator, even if it is zero bytes
+ // long:
+ locators = ['d41d8cd98f00b204e9800998ecf8427e+0'];
+ }
filename = ArvadosClient.uniqueNameForManifest(
collection.manifest_text,
'.', upload.file.name);
collection.manifest_text += '. ' +
- upload.locators.join(' ') +
+ locators.join(' ') +
' 0:' + upload.file.size.toString() + ':' +
filename +
'\n';
collection:
{ manifest_text:
collection.manifest_text }
- }).
- then(deferred.resolve);
- }, onQueueReject).then(function() {
- // Push the completed upload(s) to the bottom of the queue.
+ });
+ }).
+ then(function() {
+ // Mark the completed upload(s) as Done and push
+ // them to the bottom of the queue.
var i, qLen = $scope.uploadQueue.length;
for (i=0; i<qLen; i++) {
if (uploads.indexOf($scope.uploadQueue[i]) >= 0) {
- $scope.uploadQueue[i].committed = true;
+ $scope.uploadQueue[i].state = 'Done';
$scope.uploadQueue.push.apply(
$scope.uploadQueue,
$scope.uploadQueue.splice(i, 1));
--qLen;
}
}
+ }).
+ then(_deferredAppend.resolve,
+ _deferredAppend.reject);
+ return _deferredAppend.promise().
+ always(function() {
+ _deferredAppend = null;
});
- return deferred.promise.then(doQueueWork);
}
}
}