X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/f59d6d76acf9c6f5bb95c5902b2c9a1cca427e93..c2a1539e64937b126c0090bc5d9fe8d65411be8f:/apps/workbench/app/assets/javascripts/upload_to_collection.js diff --git a/apps/workbench/app/assets/javascripts/upload_to_collection.js b/apps/workbench/app/assets/javascripts/upload_to_collection.js index 267543c753..55f562d936 100644 --- a/apps/workbench/app/assets/javascripts/upload_to_collection.js +++ b/apps/workbench/app/assets/javascripts/upload_to_collection.js @@ -23,13 +23,18 @@ function UploadToCollection($scope, $filter, $q, $timeout, // Angular binding doesn't work its usual magic for file // inputs, so we need to $scope.$apply() this update. $scope.$apply(function(){ - var i; - var insertAt; - for (insertAt=0; (insertAt<$scope.uploadQueue.length && - $scope.uploadQueue[insertAt].state != 'Done'); - insertAt++); + var i, nItemsTodo; + // Add these new files after the items already waiting + // in the queue -- but before the items that are + // 'Done' and have therefore been pushed to the + // bottom. + for (nItemsTodo = 0; + (nItemsTodo < $scope.uploadQueue.length && + $scope.uploadQueue[nItemsTodo].state !== 'Done'); ) { + nItemsTodo++; + } for (i=0; i= 0) { + ++found; } - } - return done; + }); + return found; } }); - // TODO: watch uploadQueue, abort uploads if entries disappear + //////////////////////////////// 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; @@ -81,7 +87,7 @@ function UploadToCollection($scope, $filter, $q, $timeout, return _deferred.promise(); } function resolve() { - if (that._reader.result.length != that._slice.size) { + if (that._reader.result.length !== that._slice.size) { // Sometimes we get an onload event even if the read // did not return the desired number of bytes. We // treat that as a fail. @@ -112,7 +118,20 @@ function UploadToCollection($scope, $filter, $q, $timeout, // 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() { @@ -172,11 +191,10 @@ function UploadToCollection($scope, $filter, $q, $timeout, 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. @@ -196,13 +214,13 @@ function UploadToCollection($scope, $filter, $q, $timeout, 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) { @@ -220,7 +238,10 @@ function UploadToCollection($scope, $filter, $q, $timeout, // 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]); @@ -236,9 +257,11 @@ function UploadToCollection($scope, $filter, $q, $timeout, onUploaderProgress); } function onUploaderResolve(locator, dataSize) { - if (!locator || _currentSlice.size != dataSize) { - console.log("onUploaderResolve but locator=" + locator + - " and " + _currentSlice.size + " != " + dataSize); + var sizeHint = (''+locator).split('+')[1]; + if (!locator || parseInt(sizeHint) !== dataSize) { + console.log("onUploaderResolve, but locator '" + locator + + "' with size hint '" + sizeHint + + "' does not look right for dataSize=" + dataSize); return onUploaderReject({ textStatus: "error", err: "Bad response from slice upload" @@ -253,7 +276,8 @@ function UploadToCollection($scope, $filter, $q, $timeout, that.state = 'Paused'; setProgress(_readPos); _currentUploader = null; - _deferred.reject(reason); + if (_deferred) + _deferred.reject(reason); } function onUploaderProgress(sliceDone, sliceSize) { setProgress(_readPos + sliceDone); @@ -263,7 +287,7 @@ function UploadToCollection($scope, $filter, $q, $timeout, _maxBlobSize, that.file.size - _readPos); setProgress(_readPos); - if (size == 0) { + if (size === 0) { return false; } var blob = that.file.slice( @@ -273,36 +297,46 @@ function UploadToCollection($scope, $filter, $q, $timeout, } 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); that.statistics = ( - '' + $filter('number')(bytesDone/1024, '0') + 'K ' + - 'at ~' + $filter('number')(kBps, '0') + 'K/s') - if (that.state == 'Paused') { + '' + $filter('number')(bytesDone/1024, '0') + ' KiB ' + + 'at ~' + $filter('number')(kBps, '0') + ' KiB/s') + if (that.state === 'Paused') { that.statistics += ', paused'; - } else if (that.state == 'Uploading') { + } else if (that.state === 'Uploading') { that.statistics += ', ETA ' + $filter('date')( 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, @@ -310,9 +344,11 @@ function UploadToCollection($scope, $filter, $q, $timeout, }); //////////////////////////////// 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( @@ -320,11 +356,16 @@ function UploadToCollection($scope, $filter, $q, $timeout, {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]; @@ -338,25 +379,35 @@ function UploadToCollection($scope, $filter, $q, $timeout, return doQueueWork(); } function doQueueWork() { - var i; - that.state = 'Running'; - that.stateReason = null; - // Push the done things to the bottom of the queue. - for (i=0; (i<$scope.uploadQueue.length && - $scope.uploadQueue[i].state == 'Done'); i++); - if (i>0) - $scope.uploadQueue.push.apply($scope.uploadQueue, $scope.uploadQueue.splice(0, i)); - // If anything is not-done, do it. + // 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); + $scope.uploadQueue[0].state !== 'Done') { + 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 (!_deferred) { + // Outcome has already been decided (by stop()). + return; + } + that.state = 'Failed'; that.stateReason = ( (reason.textStatus || 'Error') + @@ -364,7 +415,7 @@ function UploadToCollection($scope, $filter, $q, $timeout, ? (' (from ' + reason.xhr.options.url + ')') : '') + ': ' + - (reason.err || '')); + (reason.err || defaultErrorMessage)); if (reason.xhr && reason.xhr.responseText) that.stateReason += ' -- ' + reason.xhr.responseText; _deferred.reject(reason); @@ -373,7 +424,8 @@ function UploadToCollection($scope, $filter, $q, $timeout, function onQueueResolve() { that.state = 'Idle'; that.stateReason = 'Done!'; - _deferred.resolve(); + if (_deferred) + _deferred.resolve(); onQueueProgress(); } function onQueueProgress() { @@ -381,39 +433,58 @@ function UploadToCollection($scope, $filter, $q, $timeout, $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 = ''; - var upload, i; - for (i=0; i= 0) { + $scope.uploadQueue[i].state = 'Done'; + $scope.uploadQueue.push.apply( + $scope.uploadQueue, + $scope.uploadQueue.splice(i, 1)); + --i; + --qLen; + } } + }). + then(_deferredAppend.resolve, + _deferredAppend.reject); + return _deferredAppend.promise(). + always(function() { + _deferredAppend = null; }); - return deferred.promise.then(doQueueWork); } } }