// // Copyright (C) Stanislaw Adaszewski, 2020 // Contact: s.adaszewski@gmail.com // Website: https://adared.ch/wba // License: GNU Affero General Public License, Version 3 // import { h, Component } from 'preact'; import WBTable from 'wb-table'; import WBBreadcrumbs from 'wb-breadcrumbs'; import WBPagination from 'wb-pagination'; import makeArvadosRequest from 'make-arvados-request'; import wbDownloadFile from 'wb-download-file'; import WBManifestWorkerWrapper from 'wb-manifest-worker-wrapper'; function unescapeName(name) { return name.replace(/(\\\\|\\[0-9]{3})/g, (_, $1) => ($1 === '\\\\' ? '\\' : String.fromCharCode(parseInt($1.substr(1), 8)))); } function encodeURIComponentIncludingDots(s) { return encodeURIComponent(s).replace('.', '%2E'); } function endsWith(what, endings) { if (typeof(endings) === 'string') return what.endsWith(endings); if (endings instanceof Array) return endings.map(a => what.endsWith(a)).reduce((a, b) => (a || b)); throw Error('Expected second argument to be either a string or an array'); } function maskRows(rows) { return rows.map(r => r.map(c => '-')); } class WBCollectionContent extends Component { constructor(...args) { super(...args); this.state.rows = []; this.state.manifestWorker = new WBManifestWorkerWrapper(); this.state.loaded = 0; this.state.total = 0; this.state.mode = 'manifestDownload'; this.state.parsedStreams = 0; this.state.totalStreams = 1; } getUrl(params) { let res = '/collection-browse/' + ('uuid' in params ? params.uuid : this.props.uuid) + '/' + encodeURIComponentIncludingDots('collectionPath' in params ? params.collectionPath : this.props.collectionPath) + '/' + ('page' in params ? params.page : this.props.page); return res; } componentDidMount() { let { arvHost, arvToken } = this.props.app.state; let { uuid, collectionPath } = this.props; let { manifestWorker } = this.state; let select = [ 'manifest_text' ]; let prom = makeArvadosRequest(arvHost, arvToken, '/arvados/v1/collections/' + uuid + '?select=' + encodeURIComponent(JSON.stringify(select)), { 'onProgress': e => { this.setState({ 'loaded': e.loaded, 'total': e.total }); } }); prom = prom.then(xhr => { const streams = xhr.response.manifest_text.split('\n'); const paths = streams.filter(s => s).map(s => { const n = s.indexOf(' '); return unescapeName(s.substr(0, n)); }); let prom_1 = new Promise(accept => accept()); prom_1 = prom_1.then(() => { this.setState({ 'totalStreams': streams.length, 'parsedStreams': 0, 'mode': 'manifestParse' }); return manifestWorker.postMessage([ 'precreatePaths', paths ]); }); let lastListingTimestamp = new Date(0); for (let i = 0; i < streams.length; i++) { prom_1 = prom_1.then(() => manifestWorker.postMessage([ 'parseStream', streams[i] ])); prom_1 = prom_1.then(() => { if (new Date() - lastListingTimestamp < 1000) return; lastListingTimestamp = new Date(); let prom_2 = new Promise(accept => accept()); prom_2 = prom_2.then(() => manifestWorker.postMessage([ 'listDirectory', '.' + this.props.collectionPath, true ])); prom_2 = prom_2.then(e => { this.prepareRows(e.data[1]); this.setState({ 'parsedStreams': (i + 1) }); }); return prom_2; }); } prom_1 = prom_1.then(() => manifestWorker.postMessage([ 'listDirectory', '.' + this.props.collectionPath, true ])); prom_1 = prom_1.then(e => { this.state.mode = 'browsingReady'; this.prepareRows(e.data[1]); }); return prom_1; }); } componentWillReceiveProps(nextProps) { this.props = nextProps; this.setState({ rows: maskRows(this.state.rows) }); const { manifestWorker, mode } = this.state; const { collectionPath } = this.props; if (mode === 'browsingReady') { let prom = manifestWorker.postMessage([ 'listDirectory', '.' + collectionPath, true ]); prom = prom.then(e => this.prepareRows(e.data[1])); } } prepareRows(listing) { let { manifestWorker, mode } = this.state; let { collectionPath, page, itemsPerPage, app } = this.props; let { arvHost, arvToken } = app.state; const numPages = Math.ceil(listing.length / itemsPerPage); listing = listing.slice(page * itemsPerPage, page * itemsPerPage + itemsPerPage); this.setState({ 'numPages': numPages, 'rows': listing.map(item => ( (item[0] === 'd') ? [ ({ item[1] }/), 'Directory', null, (
) ] : [ item[1], 'File', filesize(item[2]), ( (mode === 'browsingReady') ? (
{ endsWith(item[1].toLowerCase(), ['.nii', '.nii.gz']) ? ( ) : null }
) : null) ] )) }); } render({ collectionPath, page }, { manifestReader, rows, numPages, loaded, total, mode, parsedStreams, totalStreams }) { return (
({ name, index })) } getItemUrl={ it => this.getUrl({ collectionPath: ('.' + collectionPath).split('/').slice(0, it.index + 1).join('/').substr(1), page: 0 }) } /> { (mode === 'manifestDownload') ? (
Downloading manifest: { filesize(loaded) }
) : (
{ mode === 'manifestParse' ? (
Parsing manifest: { parsedStreams }/{ totalStreams }
) : null } this.getUrl({ 'page': page }) } />
) }
); } } WBCollectionContent.defaultProps = { 'collectionPath': '', 'page': 0, 'itemsPerPage': 20 }; export default WBCollectionContent;