Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[stable10] [revived] Retry chunks in web UI on stalled uploads #32170

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion apps/files/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,9 @@
},
config: this._filesConfig,
enableUpload: true,
maxChunkSize: OC.appConfig.files && OC.appConfig.files.max_chunk_size
maxChunkSize: OC.appConfig.files && OC.appConfig.files.max_chunk_size,
uploadStallTimeout: OC.appConfig.files && OC.appConfig.files.upload_stall_timeout,
uploadStallRetries: OC.appConfig.files && OC.appConfig.files.upload_stall_retries
}
);
this.files.initialize();
Expand Down
135 changes: 112 additions & 23 deletions apps/files/js/file-upload.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2014
* Copyright (c) 2018
*
* This file is licensed under the Affero General Public License version 3
* or later.
Expand Down Expand Up @@ -332,10 +332,22 @@ OC.FileUpload.prototype = {
if (this.data.isChunked) {
this._deleteChunkFolder();
}
this.data.stalled = false;
this.data.abort();
this.deleteUpload();
},

/**
* retry the upload
*/
retry: function() {
if (!this.data.stalled) {
console.log('Retrying upload ' + this.id);
this.data.stalled = true;
this.data.abort();
}
},

/**
* Fail the upload
*/
Expand Down Expand Up @@ -484,6 +496,17 @@ OC.Uploader.prototype = _.extend({
*/
davClient: null,

/**
* Upload progressbar element
*
* @type Object
*/
$uploadprogressbar: null,

/**
* @type int
*/
_uploadStallTimeout: 60,
/**
* Function that will allow us to know if Ajax uploads are supported
* @link https://github.com/New-Bamboo/example-ajax-upload/blob/master/public/index.html
Expand Down Expand Up @@ -806,34 +829,40 @@ OC.Uploader.prototype = _.extend({
var self = this;
window.clearInterval(this._progressBarInterval);
$('#uploadprogresswrapper .stop').fadeOut();
$('#uploadprogressbar').fadeOut(function() {
this.$uploadprogressbar.fadeOut(function() {
self.$uploadEl.trigger(new $.Event('resized'));
});
},

_showProgressBar: function() {
$('#uploadprogressbar').fadeIn();
this.$uploadprogressbar.fadeIn();
this.$uploadEl.trigger(new $.Event('resized'));
if (this._progressBarInterval) {
window.clearInterval(this._progressBarInterval);
}
this._progressBarInterval = window.setInterval(_.bind(this._updateProgressBar, this), 1000);
this._lastProgress = 0;
this._lastProgressStalledSeconds = 0;
},

_updateProgressBar: function() {
var progress = parseInt($('#uploadprogressbar').attr('data-loaded'), 10);
var total = parseInt($('#uploadprogressbar').attr('data-total'), 10);
var progress = parseInt(this.$uploadprogressbar.attr('data-loaded'), 10);
var total = parseInt(this.$uploadprogressbar.attr('data-total'), 10);
if (progress !== this._lastProgress) {
this._lastProgress = progress;
this._lastProgressStalledSeconds = 0;
this._lastProgressTime = new Date().getTime();
} else {
if (this._lastProgressStalledSeconds < 1) {
this._lastProgressStalledSeconds++;
} else if (progress >= total) {
if (progress >= total) {
// change message if we stalled at 100%
$('#uploadprogressbar .label .desktop').text(t('core', 'Processing files...'));
this.$uploadprogressbar.find('.label .desktop').text(t('core', 'Processing files...'));
} else if (new Date().getTime() - this._lastProgressTime >= this._uploadStallTimeout * 1000 ) {
// TODO: move to "fileuploadprogress" event instead and use data.uploadedBytes
// stalling needs to be checked here because the file upload no longer triggers events
// restart upload
this.log('progress stalled'); // retry chunk (and prevent IE from dying)
_.each(this._uploads, function(upload) {
// FIXME: harden by only retry pending, not the finished ones
upload.retry();
});
}
}
},
Expand Down Expand Up @@ -870,6 +899,9 @@ OC.Uploader.prototype = _.extend({
var self = this;
options = options || {};

this._uploads = {};
this._knownDirs = {};

this.fileList = options.fileList;
this.filesClient = options.filesClient || OC.Files.getClient();
this.davClient = new OC.Files.Client({
Expand All @@ -883,10 +915,15 @@ OC.Uploader.prototype = _.extend({
if (options.url) {
this.url = options.url;
}
if (options.uploadStallTimeout) {
this._uploadStallTimeout = options.uploadStallTimeout;
}

$uploadEl = $($uploadEl);
this.$uploadEl = $uploadEl;

this.$uploadprogressbar = $('#uploadprogressbar');

if ($uploadEl.exists()) {
$('#uploadprogresswrapper .stop').on('click', function() {
self.cancelUploads();
Expand All @@ -897,10 +934,12 @@ OC.Uploader.prototype = _.extend({
dropZone: options.dropZone, // restrict dropZone to content div
autoUpload: false,
sequentialUploads: true,
maxRetries: options.uploadStallRetries || 3,
retryTimeout: 500,
//singleFileUploads is on by default, so the data.files array will always have length 1
/**
* on first add of every selection
* - check all files of originalFiles array with files in dir

* - on conflict show dialog
* - skip all -> remember as single skip action for all conflicting files
* - replace all -> remember as single replace action for all conflicting files
Expand Down Expand Up @@ -1061,6 +1100,53 @@ OC.Uploader.prototype = _.extend({
},
fail: function(e, data) {
var upload = self.getUpload(data);
if (upload && upload.data && upload.data.stalled) {
self.log('retry', e, upload);
// jQuery Widget Factory uses "namespace-widgetname" since version 1.10.0:
var fu = $(this).data('blueimp-fileupload') || $(this).data('fileupload'),
retries = upload.data.retries || 0,
retry = function () {
var uid = OC.getCurrentUser().uid;
upload.uploader.davClient.getFolderContents(
'uploads/' + uid + '/' + upload.getId()
)
.done(function (status, files) {
data.uploadedBytes = 0;
_.each(files, function(file) {
// only count numeric file names to omit .file and .file.zsync
if (!isNaN(parseFloat(file.name))
&& isFinite(file.name)
// only count full chunks
&& file.size === fu.options.maxChunkSize
) {
data.uploadedBytes += file.size;
}
});

// clear the previous data:
data.data = null;
// overwrite chunk
delete data.headers['If-None-Match'];
data.submit();
})
.fail(function (status, ex) {
self.log('failed to retry', status, ex);
fu._trigger('fail', e, data);
});
};
if (upload && upload.data && upload.data.stalled &&
data.uploadedBytes < data.files[0].size &&
retries < fu.options.maxRetries) {
retries += 1;
upload.data.retries = retries;
window.setTimeout(retry, retries * fu.options.retryTimeout);
return;
}
fu.prototype
.options.fail.call(this, e, data);
return;
}

var status = null;
if (upload) {
status = upload.getResponseStatus();
Expand Down Expand Up @@ -1158,14 +1244,14 @@ OC.Uploader.prototype = _.extend({
self.log('progress handle fileuploadstart', e, data);
$('#uploadprogresswrapper .stop').show();
$('#uploadprogresswrapper .label').show();
$('#uploadprogressbar').progressbar({value: 0});
$('#uploadprogressbar .ui-progressbar-value').
self.$uploadprogressbar.progressbar({value: 0});
self.$uploadprogressbar.find('.ui-progressbar-value').
html('<em class="label inner"><span class="desktop">'
+ t('files', 'Uploading...')
+ '</span><span class="mobile">'
+ t('files', '...')
+ '</span></em>');
$('#uploadprogressbar').tipsy({gravity:'n', fade:true, live:true});
self.$uploadprogressbar.tipsy({gravity:'n', fade:true, live:true});
self._showProgressBar();
self.trigger('start', e, data);
});
Expand All @@ -1184,25 +1270,25 @@ OC.Uploader.prototype = _.extend({
lastSize = data.loaded;
diffSize = diffSize / diffUpdate; // apply timing factor, eg. 1mb/2s = 0.5mb/s
var remainingSeconds = ((data.total - data.loaded) / diffSize);
if(remainingSeconds >= 0) {
if (isFinite(remainingSeconds) && remainingSeconds >= 0) {
bufferTotal = bufferTotal - (buffer[bufferIndex]) + remainingSeconds;
buffer[bufferIndex] = remainingSeconds; //buffer to make it smoother
bufferIndex = (bufferIndex + 1) % bufferSize;
}
var smoothRemainingSeconds = (bufferTotal / bufferSize); //seconds
var h = moment.duration(smoothRemainingSeconds, "seconds").humanize();
$('#uploadprogressbar').attr('data-loaded', data.loaded);
$('#uploadprogressbar').attr('data-total', data.total);
$('#uploadprogressbar .label .mobile').text(h);
$('#uploadprogressbar .label .desktop').text(h);
$('#uploadprogressbar').attr('original-title',
self.$uploadprogressbar.attr('data-loaded', data.loaded);
self.$uploadprogressbar.attr('data-total', data.total);
self.$uploadprogressbar.find('.label .mobile').text(h);
self.$uploadprogressbar.find('.label .desktop').text(h);
self.$uploadprogressbar.attr('original-title',
t('files', '{loadedSize} of {totalSize} ({bitrate})' , {
loadedSize: humanFileSize(data.loaded),
totalSize: humanFileSize(data.total),
bitrate: humanFileSize(data.bitrate) + '/s'
})
);
$('#uploadprogressbar').progressbar('value', progress);
self.$uploadprogressbar.progressbar('value', progress);
self.trigger('progressall', e, data);
});
fileupload.on('fileuploadstop', function(e, data) {
Expand Down Expand Up @@ -1232,6 +1318,9 @@ OC.Uploader.prototype = _.extend({
'/' + encodeURIComponent(chunkId);
delete data.contentRange;
delete data.headers['Content-Range'];

// reset retries
upload.data.retries = 0;
});
fileupload.on('fileuploadchunkdone', function(e, data) {
$(data.xhr().upload).unbind('progress');
Expand Down
4 changes: 3 additions & 1 deletion apps/files/js/filelist.js
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,9 @@
fileList: this,
filesClient: this.filesClient,
dropZone: $('#content'),
maxChunkSize: options.maxChunkSize
maxChunkSize: options.maxChunkSize,
uploadStallTimeout: options.uploadStallTimeout,
uploadStallRetries: options.uploadStallRetries
});

this.setupUploadEvents(this._uploader);
Expand Down
6 changes: 5 additions & 1 deletion apps/files/lib/App.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,12 @@ public static function getNavigationManager() {

public static function extendJsConfig($array) {
$maxChunkSize = (int)(\OC::$server->getConfig()->getAppValue('files', 'max_chunk_size', (10 * 1024 * 1024)));
$uploadStallTimeout = (int)(\OC::$server->getConfig()->getAppValue('files', 'upload_stall_timeout', 60)); // in seconds
$uploadStallRetries = (int)(\OC::$server->getConfig()->getAppValue('files', 'upload_stall_retries', 100));
$array['array']['oc_appconfig']['files'] = [
'max_chunk_size' => $maxChunkSize
'max_chunk_size' => $maxChunkSize,
'upload_stall_timeout' => $uploadStallTimeout,
'upload_stall_retries' => $uploadStallRetries
];
}
}
Loading