diff --git a/src/elements/dv-elements/contextual-content/namespace-contextual-content.html b/src/elements/dv-elements/contextual-content/namespace-contextual-content.html index d51d6f1d..8b814233 100644 --- a/src/elements/dv-elements/contextual-content/namespace-contextual-content.html +++ b/src/elements/dv-elements/contextual-content/namespace-contextual-content.html @@ -246,6 +246,10 @@ } else if (this.multipleSelection) { this.$.delete.addEventListener('tap', this._delete.bind(this)); this.$.move.addEventListener('tap', this._move.bind(this)); + // FIXME: Is this the place to figure out if any + // directories are selected and disable download if + // that's the case? + this.$.download.addEventListener('tap', this._openOrDownload.bind(this)); } else if (this.currentDir) { this.$.create.addEventListener('tap', this._create.bind(this)); this.$.metadata.addEventListener('tap', this._metadata.bind(this)); @@ -289,6 +293,7 @@ } else if (this.multipleSelection) { this.$.delete.removeEventListener('tap', this._delete.bind(this)); this.$.move.removeEventListener('tap', this._move.bind(this)); + this.$.download.removeEventListener('tap', this._openOrDownload.bind(this)); } else if (this.currentDir) { this.$.create.removeEventListener('tap', this._create.bind(this)); this.$.metadata.removeEventListener('tap', this._metadata.bind(this)); @@ -642,9 +647,11 @@ _setDisabledAttribute(id, singleSelection, multipleSelection, t) { + // FIXME: Is this the place to figure out if any directories + // are selected and disable download if that's the case? if (multipleSelection && (id === 'open' || id === 'share' || id === 'metadata' || id === 'webdavUrl' || id === 'rename' || id === 'setLabel' || - id === 'download' || id === 'changeQos' || id === 'qosInfo')) { + id === 'changeQos' || id === 'qosInfo')) { this.$[id].setAttribute('disabled', ""); } diff --git a/src/elements/dv-elements/file-sharing/shared-files-page/shared-files-page.js b/src/elements/dv-elements/file-sharing/shared-files-page/shared-files-page.js index 0cc898df..10103763 100644 --- a/src/elements/dv-elements/file-sharing/shared-files-page/shared-files-page.js +++ b/src/elements/dv-elements/file-sharing/shared-files-page/shared-files-page.js @@ -45,33 +45,7 @@ class SharedFilesPage extends DcacheViewMixins.Commons(Polymer.Element) }; this.$['shared-directory-view'].path = e.detail.file.filePath; } else if (e.detail.file.fileMetaData.fileType === "REGULAR") { - //download - const worker = new Worker('./scripts/tasks/download-task.js'); - const fileURL = this.getFileWebDavUrl(e.detail.file.filePath, "read")[0]; - worker.addEventListener('message', (file) => { - worker.terminate(); - const windowUrl = window.URL || window.webkitURL; - const url = windowUrl.createObjectURL(file.data); - const link = app.$.download; - link.href = url; - link.download = e.detail.file.fileMetaData.fileName; - link.click(); - windowUrl.revokeObjectURL(url); - }, false); - worker.addEventListener('error', (e)=> { - console.info(e); - worker.terminate(); - this.dispatchEvent(new CustomEvent('dv-namespace-show-message-toast', { - detail: {message: e.message}, bubbles: true, composed: true - })); - }, false); - this.authenticationParameters = {"scheme": "Bearer", "value": e.detail.file.macaroon}; - worker.postMessage({ - 'url' : fileURL, - 'mime' : e.detail.file.fileMetaData.fileMimeType, - 'upauth' : this.getAuthValue(), - 'return': 'blob' - }); + app._initiateDownload(e.detail.file); } } _showSharedFileList() @@ -81,4 +55,4 @@ class SharedFilesPage extends DcacheViewMixins.Commons(Polymer.Element) this.$['shared-directory-view'].classList.replace('normal', 'none'); } } -window.customElements.define(SharedFilesPage.is, SharedFilesPage); \ No newline at end of file +window.customElements.define(SharedFilesPage.is, SharedFilesPage); diff --git a/src/elements/dv-elements/files-viewer/files-viewer.html b/src/elements/dv-elements/files-viewer/files-viewer.html index 71f350f7..f1f3a3d6 100644 --- a/src/elements/dv-elements/files-viewer/files-viewer.html +++ b/src/elements/dv-elements/files-viewer/files-viewer.html @@ -236,4 +236,4 @@ } window.customElements.define(FilesViewer.is, FilesViewer); - \ No newline at end of file + diff --git a/src/scripts/dv.js b/src/scripts/dv.js index f60924db..456a3531 100644 --- a/src/scripts/dv.js +++ b/src/scripts/dv.js @@ -501,6 +501,100 @@ .set(`items.${itemIndex}.currentQos`, status); vf.shadowRoot.querySelector('#feList').notifyPath(`items.${itemIndex}.currentQos`); } + /* Start browser file download of a URL */ + function _downloadFile(url) + { + var dl = document.createElement("a"); + dl.setAttribute('href', url); + dl.setAttribute('download', ''); + dl.click(); + } + /* Initiate download of a single file object */ + app._initiateDownload = function(file) + { + const fileURL = getFileWebDavUrl(file.filePath, "read")[0]; + let authval = app.getAuthValue(); + + // Unconditionally use existing macaroon if available + let macaroon = undefined; + if (file.macaroon) { + macaroon = file.macaroon; + } + else if(file.authenticationParameters !== undefined && file.authenticationParameters.scheme === "Bearer") { + macaroon = file.authenticationParameters.value; + } + + if(macaroon !== undefined) { + let u = new URL(fileURL); + u.searchParams.append('authz', macaroon); + _downloadFile(u); + } + else if(!authval) { + /* + * No explicit auth, so using cert auth, which means we can + * just access the file directly without having the user + * re-login. + */ + _downloadFile(fileURL); + } + else { + /* + * We don't seem to be able to pass our current auth + * via a standard method that triggers the browser standard + * file-download handling, so need to create a short-lived + * Macaroon for it. + */ + const macaroonWorker = new Worker('./scripts/tasks/macaroon-request-task.js'); + macaroonWorker.addEventListener('message', (e) => { + macaroonWorker.terminate(); + _downloadFile(e.data.uri.targetWithMacaroon); + }, false); + macaroonWorker.addEventListener('error', (e) => { + macaroonWorker.terminate(); + // FIXME: Display an error dialog somehow + console.error(e); + }, false); + macaroonWorker.postMessage({ + "url": fileURL, + "body": { + "caveats": ["activity:DOWNLOAD"], + "validity": "PT10M" + }, + 'upauth' : authval, + }); + } + } + /* Initiate downloads of a multi-file selection */ + function _initiateMultiDownload(e) + { + /* For some reason we only have the fileMetaData, so need to + * fabricate the expected structure... + */ + const toDL = []; + for(const f of e.detail.file.files) { + if(f.fileType === "REGULAR") { + let n = {}; + n.fileMetaData = f; + n.filePath = e.target.currentPath.endsWith('/') ? `${e.target.currentPath}${f.fileName}`: `${e.target.currentPath}/${f.fileName}`; + if(e.detail.file.authenticationParameters !== undefined) { + n.authenticationParameters = e.detail.file.authenticationParameters; + } + toDL.push(n); + } + else { + /* FIXME: Either disable download choice if non-file + * selected, or abort and display an error to the user. + */ + console.error(`Skipping ${f.fileType} ${f.fileName}`); + } + } + for (let i = 0; i < toDL.length; i++) { + /* Need to stagger starts to allow browser to start + * this download before initiating the next one. + */ + setTimeout(app._initiateDownload, i*1000, toDL[i]); + } + } window.addEventListener('qos-in-transition', function(event) { updateFeListAndMetaDataDrawer([`${event.detail.options.targetQos}`], event.detail.options.itemIndex); @@ -624,40 +718,23 @@ app.drop(e); }); window.addEventListener('dv-namespace-open-file', function (e) { - let auth; - if (e.detail.file.authenticationParameters !== undefined) { - auth = e.detail.file.authenticationParameters; - } - if (e.detail.file.fileMetaData.fileType === "DIR") { + if (e.detail.file.fileMetaData !== undefined && e.detail.file.fileMetaData.fileType === "DIR") { + let auth; + if (e.detail.file.authenticationParameters !== undefined) { + auth = e.detail.file.authenticationParameters; + } app.ls(e.detail.file.filePath, auth); Polymer.dom.flush(); - } else { - //Download the file - const worker = new Worker('./scripts/tasks/download-task.js'); - const fileURL = getFileWebDavUrl(e.detail.file.filePath, "read")[0]; - worker.addEventListener('message', (response) => { - worker.terminate(); - const windowUrl = window.URL || window.webkitURL; - const url = windowUrl.createObjectURL(response.data); - const link = app.$.download; - link.href = url; - link.download = e.detail.file.fileMetaData.fileName; - link.click(); - windowUrl.revokeObjectURL(url); - - }, false); - worker.addEventListener('error', (err)=> { - worker.terminate(); - openToast(`${err.message}`); - }, false); - worker.postMessage({ - 'url' : fileURL, - 'mime' : e.detail.file.fileMetaData.fileMimeType, - 'upauth' : app.getAuthValue(auth), - 'return': 'blob' - }); + } else if (e.target.__data.singleSelection === true) { + app._initiateDownload(e.detail.file); + } else if (e.target.__data.multipleSelection === true) { + _initiateMultiDownload(e); + } + else { + console.error("Internal error, no matching state"); } }); + window.addEventListener('dv-namespace-open-subcontextmenu', e => app.subContextMenu(e)); window.addEventListener('dv-namespace-close-subcontextmenu', () => { app.$.centralSubContextMenu.close();