diff --git a/apps/files/ajax/download.php b/apps/files/ajax/download.php index e67635ab8532..26bab8837b43 100644 --- a/apps/files/ajax/download.php +++ b/apps/files/ajax/download.php @@ -39,4 +39,15 @@ $files_list = array($files); } +/** + * this sets a cookie to be able to recognize the start of the download + * the content must not be longer than 32 characters and must only contain + * alphanumeric characters + */ +if(isset($_GET['downloadStartSecret']) + && !isset($_GET['downloadStartSecret'][32]) + && preg_match('!^[a-zA-Z0-9]+$!', $_GET['downloadStartSecret']) === 1) { + setcookie('ocDownloadStarted', $_GET['downloadStartSecret'], time() + 20, '/'); +} + OC_Files::get($dir, $files_list, $_SERVER['REQUEST_METHOD'] == 'HEAD'); diff --git a/apps/files/css/files.css b/apps/files/css/files.css index e4bf791761d1..f2f2c5ac3bcb 100644 --- a/apps/files/css/files.css +++ b/apps/files/css/files.css @@ -583,14 +583,23 @@ a.action>img { #fileList tr:focus a.action, #fileList a.action.permanent, #fileList tr:hover a.action.no-permission:hover, -#fileList tr:focus a.action.no-permission:focus -/*#fileList .name:focus .action*/ { +#fileList tr:focus a.action.no-permission:focus, +/*#fileList .name:focus .action,*/ +/* also enforce the low opacity for disabled links that are hovered/focused */ +.ie8 #fileList a.action.disabled:hover img, +#fileList tr:hover a.action.disabled:hover, +#fileList tr:focus a.action.disabled:focus, +#fileList .name:focus a.action.disabled:focus, +#fileList a.action.disabled img { -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=50)"; filter: alpha(opacity=50); opacity: .5; display:inline; } .ie8 #fileList a.action:hover img, +#fileList tr a.action.disabled.action-download, +#fileList tr:hover a.action.disabled.action-download:hover, +#fileList tr:focus a.action.disabled.action-download:focus, #fileList tr:hover a.action:hover, #fileList tr:focus a.action:focus, #fileList .name:focus a.action:focus { @@ -599,6 +608,18 @@ a.action>img { opacity: 1; display:inline; } +#fileList tr a.action.disabled { + background: none; +} + +#selectedActionsList a.download.disabled, +#fileList tr a.action.action-download.disabled { + color: #000000; +} + +#fileList tr:hover a.action.disabled:hover * { + cursor: default; +} .summary { -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=30)"; diff --git a/apps/files/js/fileactions.js b/apps/files/js/fileactions.js index 1956fda0077a..8dd26d71c3e1 100644 --- a/apps/files/js/fileactions.js +++ b/apps/files/js/fileactions.js @@ -478,8 +478,21 @@ }, function (filename, context) { var dir = context.dir || context.fileList.getCurrentDirectory(); var url = context.fileList.getDownloadUrl(filename, dir); + + var downloadFileaction = $(context.$file).find('.fileactions .action-download'); + + // don't allow a second click on the download action + if(downloadFileaction.hasClass('disabled')) { + return; + } + if (url) { - OC.redirect(url); + var disableLoadingState = function(){ + OCA.Files.FileActions.updateFileActionSpinner(downloadFileaction, false); + }; + + OCA.Files.FileActions.updateFileActionSpinner(downloadFileaction, true); + OCA.Files.Files.handleDownload(url, disableLoadingState); } }, t('files', 'Download')); } @@ -487,6 +500,26 @@ OCA.Files.FileActions = FileActions; + /** + * Replaces the download icon with a loading spinner and vice versa + * - also adds the class disabled to the passed in element + * + * @param downloadButtonElement download fileaction + * @param {boolean} showIt whether to show the spinner(true) or to hide it(false) + */ + OCA.Files.FileActions.updateFileActionSpinner = function(downloadButtonElement, showIt) { + var icon = downloadButtonElement.find('img'), + sourceImage = icon.attr('src'); + + if(showIt) { + downloadButtonElement.addClass('disabled'); + icon.attr('src', sourceImage.replace('actions/download.svg', 'loading-small.gif')); + } else { + downloadButtonElement.removeClass('disabled'); + icon.attr('src', sourceImage.replace('loading-small.gif', 'actions/download.svg')); + } + }; + /** * File action attributes. * diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js index 417c4b9fe99e..7a09d85ac411 100644 --- a/apps/files/js/filelist.js +++ b/apps/files/js/filelist.js @@ -417,7 +417,21 @@ else { files = _.pluck(this.getSelectedFiles(), 'name'); } - OC.redirect(this.getDownloadUrl(files, dir)); + + var downloadFileaction = $('#selectedActionsList').find('.download'); + + // don't allow a second click on the download action + if(downloadFileaction.hasClass('disabled')) { + event.preventDefault(); + return; + } + + var disableLoadingState = function(){ + OCA.Files.FileActions.updateFileActionSpinner(downloadFileaction, false); + }; + + OCA.Files.FileActions.updateFileActionSpinner(downloadFileaction, true); + OCA.Files.Files.handleDownload(this.getDownloadUrl(files, dir), disableLoadingState); return false; }, diff --git a/apps/files/js/files.js b/apps/files/js/files.js index 44868e78bd0a..5e9ec53fc9d5 100644 --- a/apps/files/js/files.js +++ b/apps/files/js/files.js @@ -276,8 +276,33 @@ FileList.scrollTo(getURLParameter('scrollto')); } */ + }, + + /** + * Handles the download and calls the callback function once the download has started + * - browser sends download request and adds parameter with a token + * - server notices this token and adds a set cookie to the download response + * - browser now adds this cookie for the domain + * - JS periodically checks for this cookie and then knows when the download has started to call the callback + * + * @param {string} url download URL + * @param {function} callback function to call once the download has started + */ + handleDownload: function(url, callback) { + var randomToken = Math.random().toString(36).substring(2), + checkForDownloadCookie = function() { + if (!OC.Util.isCookieSetToValue('ocDownloadStarted', randomToken)){ + return false; + } else { + callback(); + return true; + } + }; + + OC.redirect(url + '&downloadStartSecret=' + randomToken); + OC.Util.waitFor(checkForDownloadCookie, 500); } - } + }; Files._updateStorageStatisticsDebounced = _.debounce(Files._updateStorageStatistics, 250); OCA.Files.Files = Files; diff --git a/apps/files/tests/js/fileactionsSpec.js b/apps/files/tests/js/fileactionsSpec.js index 53fa8707674a..e420ab828af0 100644 --- a/apps/files/tests/js/fileactionsSpec.js +++ b/apps/files/tests/js/fileactionsSpec.js @@ -105,7 +105,7 @@ describe('OCA.Files.FileActions tests', function() { $tr.find('.action-download').click(); expect(redirectStub.calledOnce).toEqual(true); - expect(redirectStub.getCall(0).args[0]).toEqual( + expect(redirectStub.getCall(0).args[0]).toContain( OC.webroot + '/index.php/apps/files/ajax/download.php' + '?dir=%2Fsubdir&files=testName.txt'); @@ -129,7 +129,7 @@ describe('OCA.Files.FileActions tests', function() { $tr.find('.action-download').click(); expect(redirectStub.calledOnce).toEqual(true); - expect(redirectStub.getCall(0).args[0]).toEqual( + expect(redirectStub.getCall(0).args[0]).toContain( OC.webroot + '/index.php/apps/files/ajax/download.php' + '?dir=%2Fanotherpath%2Fthere&files=testName.txt' ); diff --git a/apps/files/tests/js/filelistSpec.js b/apps/files/tests/js/filelistSpec.js index b12ac2f25172..dbd70682af97 100644 --- a/apps/files/tests/js/filelistSpec.js +++ b/apps/files/tests/js/filelistSpec.js @@ -77,8 +77,8 @@ describe('OCA.Files.FileList tests', function() { '