diff --git a/frontend/package.json b/frontend/package.json
index 199ef17..3707664 100755
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -2,6 +2,7 @@
"dependencies": {
"@fortawesome/fontawesome-free": "^5.12.0",
"bootstrap": "^4.4.1",
+ "crypto-js": "^3.1.9-1",
"filesize": "^6.0.1",
"jquery": "^3.4.1",
"js-uuid": "0.0.6",
diff --git a/frontend/rollup.config.js b/frontend/rollup.config.js
index e98dca8..7032717 100755
--- a/frontend/rollup.config.js
+++ b/frontend/rollup.config.js
@@ -43,6 +43,8 @@ export default {
'node_modules/@fortawesome/fontawesome-free/webfonts/fa-brands-400.woff2': 'dist/webfonts/fa-brands-400.woff2',
'node_modules/js-uuid/js-uuid.js': 'dist/js/js-uuid.js',
'node_modules/filesize/lib/filesize.js': 'dist/js/filesize.js',
+ 'node_modules/crypto-js/core.js': 'dist/js/crypto-js/core.js',
+ 'node_modules/crypto-js/md5.js': 'dist/js/crypto-js/md5.js',
verbose: true
}),
buble({jsx: 'h'}),
diff --git a/frontend/src/html/index.html b/frontend/src/html/index.html
index 114c934..e22169d 100755
--- a/frontend/src/html/index.html
+++ b/frontend/src/html/index.html
@@ -9,6 +9,8 @@
+
+
diff --git a/frontend/src/js/component/wb-collection-content.js b/frontend/src/js/component/wb-collection-content.js
index 49c876a..0da5890 100644
--- a/frontend/src/js/component/wb-collection-content.js
+++ b/frontend/src/js/component/wb-collection-content.js
@@ -3,6 +3,7 @@ import WBTable from 'wb-table';
import WBBreadcrumbs from 'wb-breadcrumbs';
import { WBManifestReader } from 'wb-collection-manifest';
import makeArvadosRequest from 'make-arvados-request';
+import wbDownloadFile from 'wb-download-file';
class WBCollectionContent extends Component {
constructor(...args) {
@@ -41,6 +42,7 @@ class WBCollectionContent extends Component {
prepareRows() {
let { manifestReader } = this.state;
let { collectionPath } = this.props;
+ let { arvHost, arvToken } = this.props.app.state;
//path = path.split('/');
//path = [ '.' ].concat(path);
@@ -58,7 +60,14 @@ class WBCollectionContent extends Component {
({ item[1] }),
'File',
filesize(item[2]),
- ()
+ (
+
+
+
)
]
))
});
diff --git a/frontend/src/js/misc/make-arvados-request.js b/frontend/src/js/misc/make-arvados-request.js
index b9b1e74..7783872 100644
--- a/frontend/src/js/misc/make-arvados-request.js
+++ b/frontend/src/js/misc/make-arvados-request.js
@@ -1,13 +1,25 @@
-function makeArvadosRequest(arvHost, arvToken, endpoint, method='GET', data=null,
- contentType='application/json;charset=utf-8', responseType='json') {
+function makeArvadosRequest(arvHost, arvToken, endpoint, params={}) {
+ const defaultParams = {
+ 'method': 'GET',
+ 'data': null,
+ 'contentType': 'application/json;charset=utf-8',
+ 'responseType': 'json',
+ 'useSsl': true,
+ 'requireToken': true
+ };
- if (!arvHost || !arvToken)
+ Object.keys(defaultParams).map(k => (params[k] =
+ (k in params ? params[k] : defaultParams[k])));
+ let { method, data, contentType, responseType, useSsl, requireToken } = params;
+
+ if (!(arvHost && (arvToken || !requireToken)))
return new Promise((accept, reject) => reject());
let xhr = new XMLHttpRequest();
- xhr.open(method, 'https://' + arvHost + endpoint);
- xhr.setRequestHeader('Authorization', 'OAuth2 ' + arvToken);
+ xhr.open(method, (useSsl ? 'https://' : 'http://') + arvHost + endpoint);
+ if (arvToken)
+ xhr.setRequestHeader('Authorization', 'OAuth2 ' + arvToken);
if (data !== null)
xhr.setRequestHeader('Content-Type', contentType);
xhr.responseType = responseType;
diff --git a/frontend/src/js/misc/wb-collection-manifest.js b/frontend/src/js/misc/wb-collection-manifest.js
index 904984f..68fb05e 100644
--- a/frontend/src/js/misc/wb-collection-manifest.js
+++ b/frontend/src/js/misc/wb-collection-manifest.js
@@ -161,6 +161,19 @@ class WBManifestReader {
out = b;
}
}*/
+ getFile(path) {
+ if (typeof(path) === 'string')
+ path = path.split('/');
+ if (path.length < 2)
+ throw Error('Invalid file path');
+ let name = path[path.length - 1];
+ let dir = this.findDir(path.slice(0, path.length - 1));
+ if (!(name in dir))
+ throw Error('File not found');
+ if (!(dir[name] instanceof Array))
+ throw Error('Path points to a directory not a file');
+ return dir[name];
+ }
}
export { WBManifestReader };
diff --git a/frontend/src/js/misc/wb-download-file.js b/frontend/src/js/misc/wb-download-file.js
new file mode 100644
index 0000000..c64cfca
--- /dev/null
+++ b/frontend/src/js/misc/wb-download-file.js
@@ -0,0 +1,65 @@
+import makeArvadosRequest from 'make-arvados-request';
+
+function rdvHash(serviceId, locator) {
+ let blockHash = /^[0-9a-f]{32}/.exec(locator);
+ if (!blockHash)
+ throw Error('Invalid locator');
+ if (typeof(serviceId) !== 'string')
+ throw Error('Invalid service ID');
+ let res = CryptoJS.MD5(serviceId + blockHash).toString();
+ return res;
+}
+
+function wbDownloadFile(arvHost, arvToken,
+ manifestReader, path) {
+
+ const file = manifestReader.getFile(path);
+ const name = path.split('/').reverse()[0];
+ const blockRefs = file[0];
+ let services;
+
+ let prom = makeArvadosRequest(arvHost, arvToken,
+ '/arvados/v1/keep_services/accessible');
+ prom = prom.then(xhr => (services = xhr.response['items']));
+
+ const blocks = [];
+ for (let i = 0; i < blockRefs.length; i++) {
+ prom = prom.then(() => {
+ const [ locator, position, size ] = blockRefs[i];
+ const weights = services.map(s => rdvHash(s['uuid'], locator));
+ const order = Object.keys(services).sort((a, b) => weights[b].localeCompare(weights[a]));
+ const orderedServices = order.map(i => services[i]);
+
+ let k = 0;
+ const cb = () => {
+ if (k >= orderedServices.length)
+ throw Error('Block not found');
+
+ const svc = orderedServices[k];
+ k++;
+
+ let prom_1 = makeArvadosRequest(svc.service_host +
+ ':' + svc.service_port, arvToken,
+ '/' + locator, { 'useSsl': svc.service_ssl_flag,
+ 'responseType': 'arraybuffer' });
+ //prom_1 = prom_1.then(xhr => xhr.response);
+ prom_1 = prom_1.catch(cb);
+
+ return prom_1;
+ };
+
+ return cb().then(xhr => (blocks.append(xhr.response.slice(position, size))));
+ });
+ }
+
+ prom = prom.then(() => {
+ const blob = new Blob(blocks);
+ const url = window.URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = name;
+
+ });
+}
+
+export default wbDownloadFile;