Skip to content

Commit

Permalink
Merge pull request #40060 from nextcloud/backport/27-upload-progress
Browse files Browse the repository at this point in the history
[stable27] Improve upload progress visualization
  • Loading branch information
susnux authored Aug 28, 2023
2 parents e8b3b77 + eee7cbf commit 68ae870
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 20 deletions.
2 changes: 1 addition & 1 deletion apps/files/css/merged.css

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion apps/files/css/upload.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion apps/files/css/upload.scss
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
}
#uploadprogressbar .ui-progressbar-value.ui-widget-header.ui-corner-left {
height: calc(100% + 2px);
top: -1px;
top: -2px;
left: -1px;
position: absolute;
overflow: hidden;
Expand Down
74 changes: 57 additions & 17 deletions apps/files/js/file-upload.js
Original file line number Diff line number Diff line change
Expand Up @@ -618,8 +618,10 @@ OC.Uploader.prototype = _.extend({
});
if (!self._uploading) {
self.totalToUpload = 0;
self.totalUploadCount = 0;
}
self.totalToUpload += _.reduce(uploads, function(memo, upload) { return memo+upload.getFile().size; }, 0);
self.totalUploadCount += uploads.length;
var semaphore = new OCA.Files.Semaphore(5);
var promises = _.map(uploads, function(upload) {
return semaphore.acquire().then(function(){
Expand Down Expand Up @@ -726,8 +728,9 @@ OC.Uploader.prototype = _.extend({
},

showUploadCancelMessage: _.debounce(function() {
OC.Notification.show(t('files', 'Upload cancelled.'), {timeout : 7, type: 'error'});
OC.Notification.show(t('files', 'Upload cancelled.'), { timeout : 7000, type: 'error' });
}, 500),

/**
* callback for the conflicts dialog
*/
Expand Down Expand Up @@ -955,6 +958,7 @@ OC.Uploader.prototype = _.extend({
type: 'PUT',
dropZone: options.dropZone, // restrict dropZone to content div
autoUpload: false,
progressInterval: 300, // increased from the default of 100ms for more stable behvaviour when predicting remaining time
sequentialUploads: false,
limitConcurrentUploads: 4,
/**
Expand Down Expand Up @@ -1197,7 +1201,7 @@ OC.Uploader.prototype = _.extend({

if (this._supportAjaxUploadWithProgress()) {
//remaining time
var lastUpdate, lastSize, bufferSize, buffer, bufferIndex, bufferIndex2, bufferTotal;
var lastUpdate, lastSize, bufferSize, buffer, bufferIndex, bufferTotal, smoothRemainingSeconds, smoothBitrate;

var dragging = false;

Expand All @@ -1215,11 +1219,15 @@ OC.Uploader.prototype = _.extend({
// initial remaining time variables
lastUpdate = new Date().getTime();
lastSize = 0;
bufferSize = 20;
bufferSize = 20; // length of the ring buffer
buffer = [];
bufferIndex = 0;
bufferIndex2 = 0;
bufferIndex = 0; // index of the ring buffer, runs from 0 to bufferSize continuously
bufferTotal = 0;
newTotal = 0;
smoothing = 0.02; // smoothing factor for EMA
h = '';
bufferFilled = false;

for(var i = 0; i < bufferSize; i++){
buffer[i] = 0;
}
Expand All @@ -1238,33 +1246,65 @@ OC.Uploader.prototype = _.extend({
var diffUpdate = (thisUpdate - lastUpdate)/1000; // eg. 2s
lastUpdate = thisUpdate;
var diffSize = data.loaded - lastSize;
if (diffSize <= 0) {
diffSize = lastSize;
}
lastSize = data.loaded;
diffSize = diffSize / diffUpdate; // apply timing factor, eg. 1MiB/2s = 0.5MiB/s, unit is byte per second
var remainingSeconds = ((total - data.loaded) / diffSize);

if(remainingSeconds >= 0) {
// bufferTotal holds the sum of all entries in the buffer, initially 0 like the entries itself
// substract current entry from total and add the current value to total
bufferTotal = bufferTotal - (buffer[bufferIndex]) + remainingSeconds;
// put current value to the entry
buffer[bufferIndex] = remainingSeconds; //buffer to make it smoother

bufferIndex = (bufferIndex + 1) % bufferSize;
bufferIndex2++;
}
var smoothRemainingSeconds;
if (bufferIndex2 > 0 && bufferIndex2 < 20) {
smoothRemainingSeconds = bufferTotal / bufferIndex2;
} else if (bufferSize > 0) {
smoothRemainingSeconds = bufferTotal / bufferSize;
if (bufferIndex === bufferSize - 1) {
bufferFilled = true;
}
//console.log('#', ' idx: ',bufferIndex, ' Total: ', bufferTotal, ' remainSeconds: ', remainingSeconds, ' during: ', diffUpdate);

if (smoothRemainingSeconds) {
smoothRemainingSeconds = smoothing * (bufferTotal / bufferSize) + ((1-smoothing) * smoothRemainingSeconds);
} else {
smoothRemainingSeconds = 1;
smoothRemainingSeconds = bufferTotal / bufferSize;
}

var h = moment.duration(smoothRemainingSeconds, "seconds").humanize();
if (!(smoothRemainingSeconds >= 0 && smoothRemainingSeconds < 14400)) {
// show "Uploading ..." for durations longer than 4 hours
h = t('files', 'Uploading …');
// the number of currently running uploads
const runningUploads = Object.keys(self._uploads).length;

// Only show remaining time if enough buffer information is available and debounce to 1/4
if (bufferFilled && bufferIndex % 4 === 0) {
h = moment.duration(smoothRemainingSeconds, "seconds").humanize({m: 50, h: 50});
if (self.totalUploadCount > 1) {
h = t('files', '{remainingTime} ({currentNumber}/{total})', { remainingTime: h, currentNumber: self.totalUploadCount - runningUploads + 1, total: self.totalUploadCount });
}
}

// wait for the buffer to be filled and also show "Uploading ..." for durations longer than 4 hours
if (!bufferFilled || !(smoothRemainingSeconds >= 0 && smoothRemainingSeconds < 14400)) {
// Do not show file index when there is just one
if (self.totalUploadCount > 1) {
h = t('files', 'Uploading … ({currentNumber}/{total})', { currentNumber: self.totalUploadCount - runningUploads + 1, total: self.totalUploadCount });
} else {
h = t('files', 'Uploading …');
}
}

// smooth bitrate
if (smoothBitrate) {
smoothBitrate = smoothing * data.bitrate + ((1-smoothing) * smoothBitrate);
} else {
smoothBitrate = data.bitrate;
}

self._setProgressBarText(h, h, t('files', '{loadedSize} of {totalSize} ({bitrate})' , {
loadedSize: OC.Util.humanFileSize(data.loaded),
totalSize: OC.Util.humanFileSize(total),
bitrate: OC.Util.humanFileSize(data.bitrate / 8) + '/s'
bitrate: OC.Util.humanFileSize(smoothBitrate / 8) + '/s'
}));
self._setProgressBarValue(progress);
self.trigger('progressall', e, data);
Expand Down

0 comments on commit 68ae870

Please sign in to comment.