@@ -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", | |||
@@ -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'}), | |||
@@ -9,6 +9,8 @@ | |||
<script language="javascript" src="/js/fontawesome.min.js"></script> | |||
<script language="javascript" src="/js/js-uuid.js"></script> | |||
<script language="javascript" src="/js/filesize.js"></script> | |||
<script language="javascript" src="/js/crypto-js/core.js"></script> | |||
<script language="javascript" src="/js/crypto-js/md5.js"></script> | |||
</head> | |||
<body> | |||
<script language="javascript" src="/js/app.min.js"></script> | |||
@@ -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 { | |||
(<a href={ this.getUrl({ 'collectionPath': collectionPath + '/' + item[1] }) }>{ item[1] }</a>), | |||
'File', | |||
filesize(item[2]), | |||
(<div></div>) | |||
(<div> | |||
<button class="btn btn-outline-primary mx-1" title="Download" | |||
onclick={ () => wbDownloadFile(arvHost, arvToken, manifestReader, | |||
'.' + collectionPath + '/' + item[1]) }><i class="fas fa-download"></i></button> | |||
<button class="btn btn-outline-primary mx-1" title="View" | |||
onclick={ () => wbDownloadFile(arvHost, arvToken, manifestReader, | |||
'.' + collectionPath + '/' + item[1]) }><i class="far fa-eye"></i></button> | |||
</div>) | |||
] | |||
)) | |||
}); | |||
@@ -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; | |||
@@ -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 }; |
@@ -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; |