Merge pull request #12652 from tomasz-grobelny/operation_progress_improvements3

Operation progress improvements
This commit is contained in:
John Molakvoæ 2019-02-14 14:24:20 +01:00 committed by GitHub
commit 6a3f4e4957
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 523 additions and 286 deletions

View file

@ -428,6 +428,11 @@ OC.Uploader.prototype = _.extend({
*/
fileList: null,
/**
* @type OCA.Files.OperationProgressBar
*/
progressBar: null,
/**
* @type OC.Files.Client
*/
@ -759,14 +764,6 @@ OC.Uploader.prototype = _.extend({
callbacks.onNoConflicts(selection);
},
_hideProgressBar: function() {
var self = this;
$('#uploadprogresswrapper .stop').fadeOut();
$('#uploadprogressbar').fadeOut(function() {
self.$uploadEl.trigger(new $.Event('resized'));
});
},
_updateProgressBarOnUploadStop: function() {
if (this._pendingUploadDoneCount === 0) {
// All the uploads ended and there is no pending operation, so hide
@ -780,17 +777,31 @@ OC.Uploader.prototype = _.extend({
return;
}
$('#uploadprogressbar .label .mobile').text(t('core', '…'));
$('#uploadprogressbar .label .desktop').text(t('core', 'Processing files …'));
this._setProgressBarText(t('core', 'Processing files …'), t('core', '…'));
// Nothing is being uploaded at this point, and the pending operations
// can not be cancelled, so the cancel button should be hidden.
$('#uploadprogresswrapper .stop').fadeOut();
this._hideCancelButton();
},
_hideProgressBar: function() {
this.progressBar.hideProgressBar();
},
_hideCancelButton: function() {
this.progressBar.hideCancelButton();
},
_showProgressBar: function() {
$('#uploadprogressbar').fadeIn();
this.$uploadEl.trigger(new $.Event('resized'));
this.progressBar.showProgressBar();
},
_setProgressBarValue: function(value) {
this.progressBar.setProgressBarValue(value);
},
_setProgressBarText: function(textDesktop, textMobile, title) {
this.progressBar.setProgressBarText(textDesktop, textMobile, title);
},
/**
@ -826,6 +837,7 @@ OC.Uploader.prototype = _.extend({
options = options || {};
this.fileList = options.fileList;
this.progressBar = options.progressBar;
this.filesClient = options.filesClient || OC.Files.getClient();
this.davClient = new OC.Files.Client({
host: this.filesClient.getHost(),
@ -839,7 +851,7 @@ OC.Uploader.prototype = _.extend({
this.$uploadEl = $uploadEl;
if ($uploadEl.exists()) {
$('#uploadprogresswrapper .stop').on('click', function() {
this.progressBar.on('cancel', function() {
self.cancelUploads();
});
@ -1099,16 +1111,8 @@ OC.Uploader.prototype = _.extend({
// add progress handlers
fileupload.on('fileuploadstart', function(e, data) {
self.log('progress handle fileuploadstart', e, data);
$('#uploadprogresswrapper .stop').show();
$('#uploadprogresswrapper .label').show();
$('#uploadprogressbar').progressbar({value: 0});
$('#uploadprogressbar .ui-progressbar-value').
html('<em class="label inner"><span class="desktop">'
+ t('files', 'Uploading …')
+ '</span><span class="mobile">'
+ t('files', '…')
+ '</span></em>');
$('#uploadprogressbar').tooltip({placement: 'bottom'});
self._setProgressBarText(t('files', 'Uploading …'), t('files', '…'));
self._setProgressBarValue(0);
self._showProgressBar();
// initial remaining time variables
lastUpdate = new Date().getTime();
@ -1158,16 +1162,12 @@ OC.Uploader.prototype = _.extend({
// show "Uploading ..." for durations longer than 4 hours
h = t('files', 'Uploading …');
}
$('#uploadprogressbar .label .mobile').text(h);
$('#uploadprogressbar .label .desktop').text(h);
$('#uploadprogressbar').attr('original-title',
t('files', '{loadedSize} of {totalSize} ({bitrate})' , {
self._setProgressBarText(h, h, t('files', '{loadedSize} of {totalSize} ({bitrate})' , {
loadedSize: humanFileSize(data.loaded),
totalSize: humanFileSize(data.total),
bitrate: humanFileSize(data.bitrate / 8) + '/s'
})
);
$('#uploadprogressbar').progressbar('value', progress);
}));
self._setProgressBarValue(progress);
self.trigger('progressall', e, data);
});
fileupload.on('fileuploadstop', function(e, data) {

View file

@ -386,11 +386,16 @@
});
}
this._operationProgressBar = new OCA.Files.OperationProgressBar();
this._operationProgressBar.render();
this.$el.find('#uploadprogresswrapper').replaceWith(this._operationProgressBar.$el);
if (options.enableUpload) {
// TODO: auto-create this element
var $uploadEl = this.$el.find('#file_upload_start');
if ($uploadEl.exists()) {
this._uploader = new OC.Uploader($uploadEl, {
progressBar: this._operationProgressBar,
fileList: this,
filesClient: this.filesClient,
dropZone: $('#content'),
@ -2206,21 +2211,39 @@
remove: function(name, options){
options = options || {};
var fileEl = this.findFileEl(name);
var fileId = fileEl.data('id');
var index = fileEl.index();
if (!fileEl.length) {
return null;
var fileData = _.findWhere(this.files, {name: name});
if (!fileData) {
return;
}
var fileId = fileData.id;
if (this._selectedFiles[fileId]) {
// remove from selection first
this._selectFileEl(fileEl, false);
this.updateSelectionSummary();
}
if (this._selectedFiles[fileId]) {
delete this._selectedFiles[fileId];
this._selectionSummary.remove(fileData);
this.updateSelectionSummary();
}
var index = this.files.findIndex(function(el){return el.name==name;});
this.files.splice(index, 1);
// TODO: improve performance on batch update
this.isEmpty = !this.files.length;
if (typeof(options.updateSummary) === 'undefined' || !!options.updateSummary) {
this.updateEmptyContent();
this.fileSummary.remove({type: fileData.type, size: fileData.size}, true);
}
if (!fileEl.length) {
return null;
}
if (this._dragOptions && (fileEl.data('permissions') & OC.PERMISSION_DELETE)) {
// file is only draggable when delete permissions are set
fileEl.find('td.filename').draggable('destroy');
}
this.files.splice(index, 1);
if (this._currentFileModel && this._currentFileModel.get('id') === fileId) {
// Note: in the future we should call destroy() directly on the model
// and the model will take care of the deletion.
@ -2230,12 +2253,6 @@
this._updateDetailsView(null);
}
fileEl.remove();
// TODO: improve performance on batch update
this.isEmpty = !this.files.length;
if (typeof(options.updateSummary) === 'undefined' || !!options.updateSummary) {
this.updateEmptyContent();
this.fileSummary.remove({type: fileEl.attr('data-type'), size: fileEl.attr('data-size')}, true);
}
var lastIndex = this.$fileList.children().length;
// if there are less elements visible than one page
@ -2282,29 +2299,6 @@
fileNames = [fileNames];
}
function Semaphore(max) {
var counter = 0;
var waiting = [];
this.acquire = function() {
if(counter < max) {
counter++;
return new Promise(function(resolve) { resolve(); });
} else {
return new Promise(function(resolve) { waiting.push(resolve); });
}
};
this.release = function() {
counter--;
if (waiting.length > 0 && counter < max) {
counter++;
var promise = waiting.shift();
promise();
}
};
}
var moveFileFunction = function(fileName) {
var $tr = self.findFileEl(fileName);
self.showFileBusyState($tr, true);
@ -2316,7 +2310,7 @@
return self.filesClient.move(dir + fileName, targetPath + fileName)
.done(function() {
// if still viewing the same directory
if (OC.joinPaths(self.getCurrentDirectory(), '/') === dir) {
if (OC.joinPaths(self.getCurrentDirectory(), '/') === OC.joinPaths(dir, '/')) {
// recalculate folder size
var oldFile = self.findFileEl(target);
var newFile = self.findFileEl(fileName);
@ -2325,7 +2319,6 @@
oldFile.data('size', newSize);
oldFile.find('td.filesize').text(OC.Util.humanFileSize(newSize));
// TODO: also update entry in FileList.files
self.remove(fileName);
}
})
@ -2345,22 +2338,33 @@
self.showFileBusyState($tr, false);
});
};
return this.reportOperationProgress(fileNames, moveFileFunction, callback);
},
var mcSemaphore = new Semaphore(10);
_reflect: function (promise){
return promise.then(function(v){ return {};}, function(e){ return {};});
},
reportOperationProgress: function (fileNames, operationFunction, callback){
var self = this;
self._operationProgressBar.showProgressBar(false);
var mcSemaphore = new OCA.Files.Semaphore(5);
var counter = 0;
var promises = _.map(fileNames, function(arg) {
return mcSemaphore.acquire().then(function(){
moveFileFunction(arg).then(function(){
return operationFunction(arg).always(function(){
mcSemaphore.release();
counter++;
self._operationProgressBar.setProgressBarValue(100.0*counter/fileNames.length);
});
});
});
return Promise.all(promises).then(function(){
return Promise.all(_.map(promises, self._reflect)).then(function(){
if (callback) {
callback();
}
self._operationProgressBar.hideProgressBar();
});
},
@ -2385,7 +2389,7 @@
if (!_.isArray(fileNames)) {
fileNames = [fileNames];
}
_.each(fileNames, function(fileName) {
var copyFileFunction = function(fileName) {
var $tr = self.findFileEl(fileName);
self.showFileBusyState($tr, true);
if (targetPath.charAt(targetPath.length - 1) !== '/') {
@ -2441,12 +2445,12 @@
}
}
}
self.filesClient.copy(dir + fileName, targetPathAndName)
return self.filesClient.copy(dir + fileName, targetPathAndName)
.done(function () {
filesToNotify.push(fileName);
// if still viewing the same directory
if (OC.joinPaths(self.getCurrentDirectory(), '/') === dir) {
if (OC.joinPaths(self.getCurrentDirectory(), '/') === OC.joinPaths(dir, '/')) {
// recalculate folder size
var oldFile = self.findFileEl(target);
var newFile = self.findFileEl(fileName);
@ -2513,11 +2517,8 @@
}
}
});
});
if (callback) {
callback();
}
};
return this.reportOperationProgress(fileNames, copyFileFunction, callback);
},
/**
@ -2939,9 +2940,6 @@
// delete all files in directory
files = _.pluck(this.files, 'name');
}
if (files) {
this.showFileBusyState(files, true);
}
// Finish any existing actions
if (this.lastAction) {
this.lastAction();
@ -2949,43 +2947,38 @@
dir = dir || this.getCurrentDirectory();
function removeFromList(file) {
var fileEl = self.remove(file, {updateSummary: false});
// FIXME: not sure why we need this after the
// element isn't even in the DOM any more
fileEl.find('.selectCheckBox').prop('checked', false);
fileEl.removeClass('selected');
self.fileSummary.remove({type: fileEl.attr('data-type'), size: fileEl.attr('data-size')});
// TODO: this info should be returned by the ajax call!
self.updateEmptyContent();
self.fileSummary.update();
self.updateSelectionSummary();
// FIXME: don't repeat this, do it once all files are done
self.updateStorageStatistics();
self.updateStorageQuotas();
}
_.each(files, function(file) {
self.filesClient.remove(dir + '/' + file)
var removeFunction = function(fileName) {
var $tr = self.findFileEl(fileName);
self.showFileBusyState($tr, true);
return self.filesClient.remove(dir + '/' + fileName)
.done(function() {
removeFromList(file);
if (OC.joinPaths(self.getCurrentDirectory(), '/') === OC.joinPaths(dir, '/')) {
self.remove(fileName);
}
})
.fail(function(status) {
if (status === 404) {
// the file already did not exist, remove it from the list
removeFromList(file);
if (OC.joinPaths(self.getCurrentDirectory(), '/') === OC.joinPaths(dir, '/')) {
self.remove(fileName);
}
} else {
// only reset the spinner for that one file
OC.Notification.show(t('files', 'Error deleting file "{fileName}".',
{fileName: file}), {type: 'error'}
{fileName: fileName}), {type: 'error'}
);
var deleteAction = self.findFileEl(file).find('.action.delete');
deleteAction.removeClass('icon-loading-small').addClass('icon-delete');
self.showFileBusyState(files, false);
}
})
.always(function() {
self.showFileBusyState($tr, false);
});
});
};
return this.reportOperationProgress(files, removeFunction).then(function(){
self.updateStorageStatistics();
self.updateStorageQuotas();
});
},
/**
* Creates the file summary section
*/

View file

@ -21,7 +21,9 @@
"sidebarpreviewmanager.js",
"sidebarpreviewtext.js",
"detailtabview.js",
"semaphore.js",
"mainfileinfodetailview.js",
"operationprogressbar.js",
"detailsview.js",
"fileactions.js",
"fileactionsmenu.js",

View file

@ -0,0 +1,79 @@
/*
* Copyright (c) 2018
*
* This file is licensed under the Affero General Public License version 3
* or later.
*
* See the COPYING-README file.
*
*/
(function() {
var OperationProgressBar = OC.Backbone.View.extend({
tagName: 'div',
id: 'uploadprogresswrapper',
events: {
'click button.stop': '_onClickCancel'
},
render: function() {
this.$el.html(OCA.Files.Templates['operationprogressbar']({
textCancelButton: t('Cancel operation')
}));
this.setProgressBarText(t('Uploading …'), t('…'));
},
hideProgressBar: function() {
var self = this;
$('#uploadprogresswrapper .stop').fadeOut();
$('#uploadprogressbar').fadeOut(function() {
self.$el.trigger(new $.Event('resized'));
});
},
hideCancelButton: function() {
$('#uploadprogresswrapper .stop').fadeOut(function() {
this.$el.trigger(new $.Event('resized'));
});
},
showProgressBar: function(showCancelButton) {
if (showCancelButton) {
showCancelButton = true;
}
$('#uploadprogressbar').progressbar({value: 0});
if(showCancelButton) {
$('#uploadprogresswrapper .stop').show();
} else {
$('#uploadprogresswrapper .stop').hide();
}
$('#uploadprogresswrapper .label').show();
$('#uploadprogressbar').fadeIn();
this.$el.trigger(new $.Event('resized'));
},
setProgressBarValue: function(value) {
$('#uploadprogressbar').progressbar({value: value});
},
setProgressBarText: function(textDesktop, textMobile, title) {
var labelHtml = OCA.Files.Templates['operationprogressbarlabel']({textDesktop: textDesktop, textMobile: textMobile});
$('#uploadprogressbar .ui-progressbar-value').html(labelHtml);
$('#uploadprogressbar .ui-progressbar-value>em').addClass('inner');
$('#uploadprogressbar>em').replaceWith(labelHtml);
$('#uploadprogressbar>em').addClass('outer');
$('#uploadprogressbar').tooltip({placement: 'bottom'});
if(title) {
$('#uploadprogressbar').attr('original-title', title);
}
$('#uploadprogresswrapper .stop').show();
},
_onClickCancel: function (event) {
this.trigger('cancel');
return false;
}
});
OCA.Files.OperationProgressBar = OperationProgressBar;
})(OC, OCA);

View file

@ -0,0 +1,37 @@
/*
* Copyright (c) 2018
*
* This file is licensed under the Affero General Public License version 3
* or later.
*
* See the COPYING-README file.
*
*/
(function(){
var Semaphore = function(max) {
var counter = 0;
var waiting = [];
this.acquire = function() {
if(counter < max) {
counter++;
return new Promise(function(resolve) { resolve(); });
} else {
return new Promise(function(resolve) { waiting.push(resolve); });
}
};
this.release = function() {
counter--;
if (waiting.length > 0 && counter < max) {
counter++;
var promise = waiting.shift();
promise();
}
};
};
OCA.Files.Semaphore = Semaphore;
})();

View file

@ -245,6 +245,22 @@ templates['newfilemenu_filename_form'] = template({"compiler":[7,">= 4.0.0"],"ma
+ alias4(((helper = (helper = helpers.fileName || (depth0 != null ? depth0.fileName : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"fileName","hash":{},"data":data}) : helper)))
+ "\" autocomplete=\"off\" autocapitalize=\"off\">\n <input type=\"submit\" value=\" \" class=\"icon-confirm\" />\n</form>\n";
},"useData":true});
templates['operationprogressbar'] = template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
var helper;
return "<div id=\"uploadprogressbar\">\n <em class=\"label outer\" style=\"display:none\"></em>\n</div>\n<button class=\"stop icon-close\" style=\"display:none\">\n <span class=\"hidden-visually\">"
+ container.escapeExpression(((helper = (helper = helpers.textCancelButton || (depth0 != null ? depth0.textCancelButton : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"textCancelButton","hash":{},"data":data}) : helper)))
+ "</span>\n</button>\n";
},"useData":true});
templates['operationprogressbarlabel'] = template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
var helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;
return "<em class=\"label\">\n <span class=\"desktop\">"
+ alias4(((helper = (helper = helpers.textDesktop || (depth0 != null ? depth0.textDesktop : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"textDesktop","hash":{},"data":data}) : helper)))
+ "</span>\n <span class=\"mobile\">"
+ alias4(((helper = (helper = helpers.textMobile || (depth0 != null ? depth0.textMobile : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"textMobile","hash":{},"data":data}) : helper)))
+ "</span>\n</em>\n";
},"useData":true});
templates['template_addbutton'] = template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
var helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;

View file

@ -0,0 +1,6 @@
<div id="uploadprogressbar">
<em class="label outer" style="display:none"></em>
</div>
<button class="stop icon-close" style="display:none">
<span class="hidden-visually">{{textCancelButton}}</span>
</button>

View file

@ -0,0 +1,4 @@
<em class="label">
<span class="desktop">{{textDesktop}}</span>
<span class="mobile">{{textMobile}}</span>
</em>

View file

@ -1,12 +1,6 @@
<div id="controls">
<div class="actions creatable hidden">
<div id="uploadprogresswrapper">
<div id="uploadprogressbar">
<em class="label outer" style="display:none"><span class="desktop"><?php p($l->t('Uploading …'));?></span><span class="mobile"><?php p($l->t('…'));?></span></em>
</div>
<button class="stop icon-close" style="display:none">
<span class="hidden-visually"><?php p($l->t('Cancel upload')) ?></span>
</button>
</div>
</div>
<div id="file_action_panel"></div>

View file

@ -24,6 +24,7 @@ describe('OC.Upload tests', function() {
var testFile;
var uploader;
var failStub;
var progressBarStub;
beforeEach(function() {
testFile = {
@ -45,7 +46,8 @@ describe('OC.Upload tests', function() {
'</div>'
);
$dummyUploader = $('#file_upload_start');
uploader = new OC.Uploader($dummyUploader);
progressBarStub = {on: function(){}};
uploader = new OC.Uploader($dummyUploader, {progressBar: progressBarStub});
failStub = sinon.stub();
uploader.on('fail', failStub);
});
@ -143,6 +145,7 @@ describe('OC.Upload tests', function() {
conflictDialogStub = sinon.stub(OC.dialogs, 'fileexists');
uploader = new OC.Uploader($dummyUploader, {
progressBar: progressBarStub,
fileList: fileList
});

View file

@ -522,7 +522,7 @@ describe('OCA.Files.FileList tests', function() {
beforeEach(function() {
deferredDelete = $.Deferred();
deleteStub = sinon.stub(filesClient, 'remove').returns(deferredDelete.promise());
deleteStub = sinon.stub(filesClient, 'remove');
});
afterEach(function() {
deleteStub.restore();
@ -530,92 +530,153 @@ describe('OCA.Files.FileList tests', function() {
function doDelete() {
// note: normally called from FileActions
fileList.do_delete(['One.txt', 'Two.jpg']);
return fileList.do_delete(['One.txt', 'Two.jpg']).then(function(){
expect(deleteStub.calledTwice).toEqual(true);
expect(deleteStub.getCall(0).args[0]).toEqual('/subdir/One.txt');
expect(deleteStub.getCall(1).args[0]).toEqual('/subdir/Two.jpg');
expect(deleteStub.calledTwice).toEqual(true);
expect(deleteStub.getCall(0).args[0]).toEqual('/subdir/One.txt');
expect(deleteStub.getCall(1).args[0]).toEqual('/subdir/Two.jpg');
});
}
it('calls delete.php, removes the deleted entries and updates summary', function() {
it('calls delete.php, removes the deleted entries and updates summary', function(done) {
var $summary;
fileList.setFiles(testFiles);
doDelete();
deferredDelete1 = $.Deferred();
deferredDelete2 = $.Deferred();
deleteStub.onCall(0).callsFake(function(src){
expect(deleteStub.calledOnce).toEqual(true);
expect(src).toEqual('/subdir/One.txt');
return deferredDelete1.promise();
});
deleteStub.onCall(1).callsFake(function(src){
expect(deleteStub.calledTwice).toEqual(true);
expect(src).toEqual('/subdir/Two.jpg');
return deferredDelete2.promise();
});
deferredDelete.resolve(200);
var promise = fileList.do_delete(['One.txt', 'Two.jpg']);
deferredDelete1.resolve(200);
deferredDelete2.resolve(200);
return promise.then(function(){
expect(fileList.findFileEl('One.txt').length).toEqual(0);
expect(fileList.findFileEl('Two.jpg').length).toEqual(0);
expect(fileList.findFileEl('Three.pdf').length).toEqual(1);
expect(fileList.$fileList.find('tr').length).toEqual(2);
expect(fileList.findFileEl('One.txt').length).toEqual(0);
expect(fileList.findFileEl('Two.jpg').length).toEqual(0);
expect(fileList.findFileEl('Three.pdf').length).toEqual(1);
expect(fileList.$fileList.find('tr').length).toEqual(2);
$summary = $('#filestable .summary');
expect($summary.hasClass('hidden')).toEqual(false);
expect($summary.find('.dirinfo').text()).toEqual('1 folder');
expect($summary.find('.fileinfo').text()).toEqual('1 file');
expect($summary.find('.dirinfo').hasClass('hidden')).toEqual(false);
expect($summary.find('.fileinfo').hasClass('hidden')).toEqual(false);
expect($summary.find('.filesize').text()).toEqual('57 KB');
expect(fileList.isEmpty).toEqual(false);
expect($('#filestable thead th').hasClass('hidden')).toEqual(false);
expect($('#emptycontent').hasClass('hidden')).toEqual(true);
$summary = $('#filestable .summary');
expect($summary.hasClass('hidden')).toEqual(false);
expect($summary.find('.dirinfo').text()).toEqual('1 folder');
expect($summary.find('.fileinfo').text()).toEqual('1 file');
expect($summary.find('.dirinfo').hasClass('hidden')).toEqual(false);
expect($summary.find('.fileinfo').hasClass('hidden')).toEqual(false);
expect($summary.find('.filesize').text()).toEqual('57 KB');
expect(fileList.isEmpty).toEqual(false);
expect($('#filestable thead th').hasClass('hidden')).toEqual(false);
expect($('#emptycontent').hasClass('hidden')).toEqual(true);
expect(notificationStub.notCalled).toEqual(true);
expect(notificationStub.notCalled).toEqual(true);
done();
});
});
it('shows busy state on files to be deleted', function() {
it('shows busy state on files to be deleted', function(done) {
fileList.setFiles(testFiles);
doDelete();
deferredDelete1 = $.Deferred();
deferredDelete2 = $.Deferred();
deleteStub.onCall(0).callsFake(function(src){
expect(fileList.findFileEl('One.txt').hasClass('busy')).toEqual(true);
expect(fileList.findFileEl('Three.pdf').hasClass('busy')).toEqual(false);
expect(fileList.findFileEl('One.txt').hasClass('busy')).toEqual(true);
expect(fileList.findFileEl('Three.pdf').hasClass('busy')).toEqual(false);
expect(deleteStub.calledOnce).toEqual(true);
expect(src).toEqual('/subdir/One.txt');
return deferredDelete1.promise();
});
deleteStub.onCall(1).callsFake(function(src){
expect(fileList.findFileEl('Two.jpg').hasClass('busy')).toEqual(true);
expect(fileList.findFileEl('Three.pdf').hasClass('busy')).toEqual(false);
expect(deleteStub.calledTwice).toEqual(true);
expect(src).toEqual('/subdir/Two.jpg');
return deferredDelete2.promise();
});
var promise = fileList.do_delete(['One.txt', 'Two.jpg']).then(function(){
expect(deleteStub.calledTwice).toEqual(true);
});
deferredDelete1.resolve(200);
deferredDelete2.resolve(200);
return promise.then(function(){
expect(fileList.findFileEl('One.txt').hasClass('busy')).toEqual(false);
expect(fileList.findFileEl('Two.jpg').hasClass('busy')).toEqual(false);
done();
});
});
it('shows busy state on all files when deleting all', function() {
it('shows busy state on all files when deleting all', function(done) {
fileList.setFiles(testFiles);
fileList.do_delete();
expect(fileList.$fileList.find('tr.busy').length).toEqual(4);
var deferredDeleteArray = [];
var count = 0;
for (var i = 0; i < 4; i++) {
(function(i, fn){
deferredDeleteArray.push($.Deferred());
deleteStub.onCall(i).callsFake(function(src){
expect(fileList.findFileEl(fn).hasClass('busy')).toEqual(true);
count++;
return deferredDeleteArray[i].promise();
});
})(i, testFiles[i].name);
}
var promise = fileList.do_delete();
for (var i = 0; i < 4; i++) {
deferredDeleteArray[i].resolve(200);
}
return promise.then(function(){
expect(count).toEqual(4);
done();
});
});
it('updates summary when deleting last file', function() {
it('updates summary when deleting last file', function(done) {
var $summary;
fileList.setFiles([testFiles[0], testFiles[1]]);
doDelete();
deleteStub.returns(deferredDelete.promise());
deferredDelete.resolve(200);
expect(fileList.$fileList.find('tr').length).toEqual(0);
$summary = $('#filestable .summary');
expect($summary.hasClass('hidden')).toEqual(true);
expect(fileList.isEmpty).toEqual(true);
expect(fileList.files.length).toEqual(0);
expect($('#filestable thead th').hasClass('hidden')).toEqual(true);
expect($('#emptycontent').hasClass('hidden')).toEqual(false);
return doDelete().then(function(){
expect(fileList.$fileList.find('tr').length).toEqual(0);
$summary = $('#filestable .summary');
expect($summary.hasClass('hidden')).toEqual(true);
expect(fileList.isEmpty).toEqual(true);
expect(fileList.files.length).toEqual(0);
expect($('#filestable thead th').hasClass('hidden')).toEqual(true);
expect($('#emptycontent').hasClass('hidden')).toEqual(false);
done();
});
});
it('bring back deleted item when delete call failed', function() {
it('bring back deleted item when delete call failed', function(done) {
fileList.setFiles(testFiles);
doDelete();
deleteStub.returns(deferredDelete.promise());
var promise = doDelete();
deferredDelete.reject(403);
return promise.then(function(){
// files are still in the list
expect(fileList.findFileEl('One.txt').length).toEqual(1);
expect(fileList.findFileEl('Two.jpg').length).toEqual(1);
expect(fileList.$fileList.find('tr').length).toEqual(4);
// files are still in the list
expect(fileList.findFileEl('One.txt').length).toEqual(1);
expect(fileList.findFileEl('Two.jpg').length).toEqual(1);
expect(fileList.$fileList.find('tr').length).toEqual(4);
expect(notificationStub.calledTwice).toEqual(true);
expect(notificationStub.calledTwice).toEqual(true);
done();
});
});
it('remove file from list if delete call returned 404 not found', function() {
it('remove file from list if delete call returned 404 not found', function(done) {
fileList.setFiles(testFiles);
doDelete();
deleteStub.returns(deferredDelete.promise());
var promise = doDelete();
deferredDelete.reject(404);
return promise.then(function(){
expect(fileList.findFileEl('One.txt').length).toEqual(0);
expect(fileList.findFileEl('Two.jpg').length).toEqual(0);
expect(fileList.$fileList.find('tr').length).toEqual(2);
// files are still in the list
expect(fileList.findFileEl('One.txt').length).toEqual(0);
expect(fileList.findFileEl('Two.jpg').length).toEqual(0);
expect(fileList.$fileList.find('tr').length).toEqual(2);
expect(notificationStub.notCalled).toEqual(true);
expect(notificationStub.notCalled).toEqual(true);
done();
});
});
});
describe('Renaming files', function() {
@ -831,7 +892,7 @@ describe('OCA.Files.FileList tests', function() {
beforeEach(function() {
deferredMove = $.Deferred();
moveStub = sinon.stub(filesClient, 'move').returns(deferredMove.promise());
moveStub = sinon.stub(filesClient, 'move');
fileList.setFiles(testFiles);
});
@ -840,14 +901,18 @@ describe('OCA.Files.FileList tests', function() {
});
it('Moves single file to target folder', function(done) {
return fileList.move('One.txt', '/somedir').then(function(){
var promise = fileList.move('One.txt', '/somedir');
moveStub.callsFake(function(src, dst){
expect(moveStub.calledOnce).toEqual(true);
expect(moveStub.getCall(0).args[0]).toEqual('/subdir/One.txt');
expect(moveStub.getCall(0).args[1]).toEqual('/somedir/One.txt');
expect(src).toEqual('/subdir/One.txt');
expect(dst).toEqual('/somedir/One.txt');
return deferredMove.promise();
});
deferredMove.resolve(201);
deferredMove.resolve(201);
return promise.then(function(){
expect(fileList.findFileEl('One.txt').length).toEqual(0);
// folder size has increased
@ -861,26 +926,29 @@ describe('OCA.Files.FileList tests', function() {
it('Moves list of files to target folder', function(done) {
var deferredMove1 = $.Deferred();
var deferredMove2 = $.Deferred();
moveStub.onCall(0).returns(deferredMove1.promise());
moveStub.onCall(1).returns(deferredMove2.promise());
return fileList.move(['One.txt', 'Two.jpg'], '/somedir').then(function(){
expect(moveStub.calledTwice).toEqual(true);
expect(moveStub.getCall(0).args[0]).toEqual('/subdir/One.txt');
expect(moveStub.getCall(0).args[1]).toEqual('/somedir/One.txt');
expect(moveStub.getCall(1).args[0]).toEqual('/subdir/Two.jpg');
expect(moveStub.getCall(1).args[1]).toEqual('/somedir/Two.jpg');
deferredMove1.resolve(201);
moveStub.onCall(0).callsFake(function(src, dst){
expect(moveStub.calledOnce).toEqual(true);
expect(src).toEqual('/subdir/One.txt');
expect(dst).toEqual('/somedir/One.txt');
return deferredMove1.promise();
});
moveStub.onCall(1).callsFake(function(src, dst){
expect(fileList.findFileEl('One.txt').length).toEqual(0);
// folder size has increased during move
expect(fileList.findFileEl('somedir').data('size')).toEqual(262);
expect(fileList.findFileEl('somedir').find('.filesize').text()).toEqual('262 B');
deferredMove2.resolve(201);
expect(src).toEqual('/subdir/Two.jpg');
expect(dst).toEqual('/somedir/Two.jpg');
return deferredMove2.promise();
});
var promise = fileList.move(['One.txt', 'Two.jpg'], '/somedir');
deferredMove1.resolve(201);
deferredMove2.resolve(201);
return promise.then(function(){
expect(moveStub.calledTwice).toEqual(true);
expect(fileList.findFileEl('Two.jpg').length).toEqual(0);
@ -893,34 +961,32 @@ describe('OCA.Files.FileList tests', function() {
});
});
it('Shows notification if a file could not be moved', function(done) {
return fileList.move('One.txt', '/somedir').then(function(){
moveStub.callsFake(function(){
expect(moveStub.calledOnce).toEqual(true);
deferredMove.reject(409);
return deferredMove.promise();
});
var promise = fileList.move('One.txt', '/somedir');
deferredMove.reject(409);
return promise.then(function(){
expect(fileList.findFileEl('One.txt').length).toEqual(1);
expect(notificationStub.calledOnce).toEqual(true);
expect(notificationStub.getCall(0).args[0]).toEqual('Could not move "One.txt"');
done();
});
});
it('Restores thumbnail if a file could not be moved', function(done) {
return fileList.move('One.txt', '/somedir').then(function(){
moveStub.callsFake(function(){
expect(fileList.findFileEl('One.txt').find('.thumbnail').parent().attr('class'))
.toContain('icon-loading-small');
expect(moveStub.calledOnce).toEqual(true);
deferredMove.reject(409);
return deferredMove.promise();
});
var promise = fileList.move('One.txt', '/somedir');
deferredMove.reject(409);
return promise.then(function(){
expect(fileList.findFileEl('One.txt').length).toEqual(1);
expect(notificationStub.calledOnce).toEqual(true);
expect(notificationStub.getCall(0).args[0]).toEqual('Could not move "One.txt"');
expect(OC.TestUtil.getImageUrl(fileList.findFileEl('One.txt').find('.thumbnail')))
.toEqual(OC.imagePath('core', 'filetypes/text.svg'));
done();
@ -934,7 +1000,7 @@ describe('OCA.Files.FileList tests', function() {
beforeEach(function() {
deferredCopy = $.Deferred();
copyStub = sinon.stub(filesClient, 'copy').returns(deferredCopy.promise());
copyStub = sinon.stub(filesClient, 'copy');
fileList.setFiles(testFiles);
});
@ -942,86 +1008,100 @@ describe('OCA.Files.FileList tests', function() {
copyStub.restore();
});
it('Copies single file to target folder', function() {
fileList.copy('One.txt', '/somedir');
it('Copies single file to target folder', function(done) {
copyStub.callsFake(function(){
expect(copyStub.calledOnce).toEqual(true);
expect(copyStub.getCall(0).args[0]).toEqual('/subdir/One.txt');
expect(copyStub.getCall(0).args[1]).toEqual('/somedir/One.txt');
expect(copyStub.calledOnce).toEqual(true);
expect(copyStub.getCall(0).args[0]).toEqual('/subdir/One.txt');
expect(copyStub.getCall(0).args[1]).toEqual('/somedir/One.txt');
return deferredCopy.promise();
});
var promise = fileList.copy('One.txt', '/somedir');
deferredCopy.resolve(201);
return promise.then(function(){
// File is still here
expect(fileList.findFileEl('One.txt').length).toEqual(1);
// File is still here
expect(fileList.findFileEl('One.txt').length).toEqual(1);
// folder size has increased
expect(fileList.findFileEl('somedir').data('size')).toEqual(262);
expect(fileList.findFileEl('somedir').find('.filesize').text()).toEqual('262 B');
// folder size has increased
expect(fileList.findFileEl('somedir').data('size')).toEqual(262);
expect(fileList.findFileEl('somedir').find('.filesize').text()).toEqual('262 B');
// Copying sents a notification to tell that we've successfully copied file
expect(notificationStub.notCalled).toEqual(false);
// Copying sents a notification to tell that we've successfully copied file
expect(notificationStub.notCalled).toEqual(false);
done();
});
});
it('Copies list of files to target folder', function() {
it('Copies list of files to target folder', function(done) {
var deferredCopy1 = $.Deferred();
var deferredCopy2 = $.Deferred();
copyStub.onCall(0).returns(deferredCopy1.promise());
copyStub.onCall(1).returns(deferredCopy2.promise());
copyStub.onCall(0).callsFake(function(src, dst){
expect(src).toEqual('/subdir/One.txt');
expect(dst).toEqual('/somedir/One.txt');
return deferredCopy1.promise();
});
copyStub.onCall(1).callsFake(function(src, dst){
// folder size has increased during copy
expect(fileList.findFileEl('somedir').data('size')).toEqual(262);
expect(fileList.findFileEl('somedir').find('.filesize').text()).toEqual('262 B');
fileList.copy(['One.txt', 'Two.jpg'], '/somedir');
expect(copyStub.calledTwice).toEqual(true);
expect(copyStub.getCall(0).args[0]).toEqual('/subdir/One.txt');
expect(copyStub.getCall(0).args[1]).toEqual('/somedir/One.txt');
expect(copyStub.getCall(1).args[0]).toEqual('/subdir/Two.jpg');
expect(copyStub.getCall(1).args[1]).toEqual('/somedir/Two.jpg');
expect(src).toEqual('/subdir/Two.jpg');
expect(dst).toEqual('/somedir/Two.jpg');
return deferredCopy2.promise();
});
var promise = fileList.copy(['One.txt', 'Two.jpg'], '/somedir');
deferredCopy1.resolve(201);
expect(fileList.findFileEl('One.txt').length).toEqual(1);
// folder size has increased during copy
expect(fileList.findFileEl('somedir').data('size')).toEqual(262);
expect(fileList.findFileEl('somedir').find('.filesize').text()).toEqual('262 B');
deferredCopy2.resolve(201);
expect(fileList.findFileEl('Two.jpg').length).toEqual(1);
return promise.then(function(){
expect(copyStub.calledTwice).toEqual(true);
expect(fileList.findFileEl('Two.jpg').length).toEqual(1);
expect(fileList.findFileEl('One.txt').length).toEqual(1);
// folder size has increased
expect(fileList.findFileEl('somedir').data('size')).toEqual(12311);
expect(fileList.findFileEl('somedir').find('.filesize').text()).toEqual('12 KB');
// folder size has increased
expect(fileList.findFileEl('somedir').data('size')).toEqual(12311);
expect(fileList.findFileEl('somedir').find('.filesize').text()).toEqual('12 KB');
expect(notificationStub.notCalled).toEqual(false);
expect(notificationStub.notCalled).toEqual(false);
done();
});
});
it('Shows notification if a file could not be copied', function() {
fileList.copy('One.txt', '/somedir');
expect(copyStub.calledOnce).toEqual(true);
it('Shows notification if a file could not be copied', function(done) {
copyStub.callsFake(function(){
expect(copyStub.calledOnce).toEqual(true);
return deferredCopy.promise();
});
var promise = fileList.copy('One.txt', '/somedir');
deferredCopy.reject(409);
expect(fileList.findFileEl('One.txt').length).toEqual(1);
expect(notificationStub.calledOnce).toEqual(true);
expect(notificationStub.getCall(0).args[0]).toEqual('Could not copy "One.txt"');
return promise.then(function(){
expect(fileList.findFileEl('One.txt').length).toEqual(1);
expect(notificationStub.calledOnce).toEqual(true);
expect(notificationStub.getCall(0).args[0]).toEqual('Could not copy "One.txt"');
done();
});
});
it('Restores thumbnail if a file could not be copied', function() {
fileList.copy('One.txt', '/somedir');
expect(fileList.findFileEl('One.txt').find('.thumbnail').parent().attr('class'))
.toContain('icon-loading-small');
expect(copyStub.calledOnce).toEqual(true);
it('Restores thumbnail if a file could not be copied', function(done) {
copyStub.callsFake(function(){
expect(fileList.findFileEl('One.txt').find('.thumbnail').parent().attr('class'))
.toContain('icon-loading-small');
expect(copyStub.calledOnce).toEqual(true);
return deferredCopy.promise();
});
var promise = fileList.copy('One.txt', '/somedir');
deferredCopy.reject(409);
return promise.then(function(){
expect(fileList.findFileEl('One.txt').length).toEqual(1);
expect(fileList.findFileEl('One.txt').length).toEqual(1);
expect(notificationStub.calledOnce).toEqual(true);
expect(notificationStub.getCall(0).args[0]).toEqual('Could not copy "One.txt"');
expect(notificationStub.calledOnce).toEqual(true);
expect(notificationStub.getCall(0).args[0]).toEqual('Could not copy "One.txt"');
expect(OC.TestUtil.getImageUrl(fileList.findFileEl('One.txt').find('.thumbnail')))
.toEqual(OC.imagePath('core', 'filetypes/text.svg'));
expect(OC.TestUtil.getImageUrl(fileList.findFileEl('One.txt').find('.thumbnail')))
.toEqual(OC.imagePath('core', 'filetypes/text.svg'));
done();
});
});
});
@ -2283,7 +2363,7 @@ describe('OCA.Files.FileList tests', function() {
var deleteStub, deferredDelete;
beforeEach(function() {
deferredDelete = $.Deferred();
deleteStub = sinon.stub(filesClient, 'remove').returns(deferredDelete.promise());
deleteStub = sinon.stub(filesClient, 'remove');
fileList.$el.find('.actions-selected').click();
});
@ -2292,34 +2372,54 @@ describe('OCA.Files.FileList tests', function() {
deleteStub.restore();
});
it('Deletes selected files when "Delete" clicked', function() {
it('Deletes selected files when "Delete" clicked', function(done) {
var deferred = $.Deferred();
deleteStub.returns(deferredDelete.promise());
deleteStub.onCall(2).callsFake(function(){
expect(deleteStub.callCount).toEqual(3);
expect(deleteStub.getCall(0).args[0]).toEqual('/subdir/One.txt');
expect(deleteStub.getCall(1).args[0]).toEqual('/subdir/Three.pdf');
expect(deleteStub.getCall(2).args[0]).toEqual('/subdir/somedir');
return deferredDelete.promise();
});
stub = sinon.stub(fileList._operationProgressBar, 'hideProgressBar').callsFake(function(){
expect(fileList.findFileEl('One.txt').length).toEqual(0);
expect(fileList.findFileEl('Three.pdf').length).toEqual(0);
expect(fileList.findFileEl('somedir').length).toEqual(0);
expect(fileList.findFileEl('Two.jpg').length).toEqual(1);
done();
deferred.resolve();
});
$('.selectedActions .filesSelectMenu .delete').click();
expect(deleteStub.callCount).toEqual(3);
expect(deleteStub.getCall(0).args[0]).toEqual('/subdir/One.txt');
expect(deleteStub.getCall(1).args[0]).toEqual('/subdir/Three.pdf');
expect(deleteStub.getCall(2).args[0]).toEqual('/subdir/somedir');
deferredDelete.resolve(204);
return deferred.promise();
expect(fileList.findFileEl('One.txt').length).toEqual(0);
expect(fileList.findFileEl('Three.pdf').length).toEqual(0);
expect(fileList.findFileEl('somedir').length).toEqual(0);
expect(fileList.findFileEl('Two.jpg').length).toEqual(1);
});
it('Deletes all files when all selected when "Delete" clicked', function() {
it('Deletes all files when all selected when "Delete" clicked', function(done) {
var deferred = $.Deferred();
deleteStub.returns(deferredDelete.promise());
deleteStub.onCall(3).callsFake(function(){
expect(deleteStub.getCall(0).args[0]).toEqual('/subdir/One.txt');
expect(deleteStub.getCall(1).args[0]).toEqual('/subdir/Two.jpg');
expect(deleteStub.getCall(2).args[0]).toEqual('/subdir/Three.pdf');
expect(deleteStub.getCall(3).args[0]).toEqual('/subdir/somedir');
return deferredDelete.promise();
});
stub = sinon.stub(fileList._operationProgressBar, 'hideProgressBar').callsFake(function(){
expect(fileList.isEmpty).toEqual(true);
expect(deleteStub.callCount).toEqual(4);
done();
deferred.resolve();
});
$('.select-all').click();
$('.selectedActions .filesSelectMenu .delete').click();
expect(deleteStub.callCount).toEqual(4);
expect(deleteStub.getCall(0).args[0]).toEqual('/subdir/One.txt');
expect(deleteStub.getCall(1).args[0]).toEqual('/subdir/Two.jpg');
expect(deleteStub.getCall(2).args[0]).toEqual('/subdir/Three.pdf');
expect(deleteStub.getCall(3).args[0]).toEqual('/subdir/somedir');
deferredDelete.resolve(204);
expect(fileList.isEmpty).toEqual(true);
return deferred.promise();
});
});
});

View file

@ -427,6 +427,9 @@
_getSabreException: function(response) {
var result = {};
var xml = response.xhr.responseXML;
if (xml === null) {
return result;
}
var messages = xml.getElementsByTagNameNS('http://sabredav.org/ns', 'message');
var exceptions = xml.getElementsByTagNameNS('http://sabredav.org/ns', 'exception');
if (messages.length) {