IF YOU WOULD LIKE TO GET AN ACCOUNT, please write an email to s dot adaszewski at gmail dot com. User accounts are meant only to report issues and/or generate pull requests. This is a purpose-specific Git hosting for ADARED projects. Thank you for your understanding!
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

222 lines
7.5KB

  1. import { h, Component } from 'preact';
  2. import WBTable from 'wb-table';
  3. import WBBreadcrumbs from 'wb-breadcrumbs';
  4. import WBPagination from 'wb-pagination';
  5. import WBRootDirWrapper from 'wb-rootdir-wrapper';
  6. import makeArvadosRequest from 'make-arvados-request';
  7. import wbDownloadFile from 'wb-download-file';
  8. function unescapeName(name) {
  9. return name.replace(/(\\\\|\\[0-9]{3})/g,
  10. (_, $1) => ($1 === '\\\\' ? '\\' : String.fromCharCode(parseInt($1.substr(1), 8))));
  11. }
  12. class WBCollectionContent extends Component {
  13. constructor(...args) {
  14. super(...args);
  15. this.state.rows = [];
  16. this.state.manifestWorker = new Worker('/js/wb-manifest-worker.js');
  17. this.state.manifestWorker.onerror = (e => console.log(e));
  18. this.state.loaded = 0;
  19. this.state.total = 0;
  20. this.state.mode = 'manifestDownload';
  21. this.state.parsedStreams = 0;
  22. this.state.totalStreams = 1;
  23. this.state.rootDirWrapper = null;
  24. }
  25. getUrl(params) {
  26. let res = '/collection-browse/' +
  27. ('uuid' in params ? params.uuid : this.props.uuid) + '/' +
  28. encodeURIComponent('collectionPath' in params ? params.collectionPath : this.props.collectionPath) + '/' +
  29. ('page' in params ? params.page : this.props.page);
  30. return res;
  31. }
  32. componentDidMount() {
  33. let { arvHost, arvToken } = this.props.app.state;
  34. let { uuid, collectionPath } = this.props;
  35. let { manifestWorker } = this.state;
  36. let select = [ 'manifest_text' ];
  37. let prom = makeArvadosRequest(arvHost, arvToken,
  38. '/arvados/v1/collections/' + uuid +
  39. '?select=' + encodeURIComponent(JSON.stringify(select)),
  40. { 'onProgress': e => {
  41. this.setState({ 'loaded': e.loaded, 'total': e.total });
  42. } });
  43. prom = prom.then(xhr => {
  44. const streams = xhr.response.manifest_text.split('\n');
  45. const paths = streams.filter(s => s).map(s => {
  46. const n = s.indexOf(' ');
  47. return unescapeName(s.substr(0, n));
  48. });
  49. let prom_1 = new Promise(accept => accept());
  50. prom_1 = prom_1.then(() => {
  51. const prom_2 = new Promise(accept => {
  52. manifestWorker.onmessage = () => accept();
  53. manifestWorker.postMessage([ 'precreatePaths', paths ]);
  54. this.setState({
  55. 'totalStreams': streams.length,
  56. 'parsedStreams': 0,
  57. 'mode': 'manifestParse'
  58. })
  59. })
  60. return prom_2;
  61. });
  62. for (let i = 0; i < streams.length; i++) {
  63. prom_1 = prom_1.then(() => {
  64. const prom_2 = new Promise(accept => {
  65. manifestWorker.onmessage = () => accept();
  66. manifestWorker.postMessage([ 'parseStream', streams[i] ]);
  67. });
  68. return prom_2;
  69. });
  70. prom_1 = prom_1.then(() => {
  71. const prom_2 = new Promise(accept => {
  72. manifestWorker.onmessage = (e) => accept(e);
  73. manifestWorker.postMessage([ 'listDirectory', '.' + this.props.collectionPath, true ]);
  74. if (i % 1000 === 0)
  75. console.log(i + '/' + streams.length);
  76. });
  77. return prom_2;
  78. });
  79. prom_1 = prom_1.then(e => {
  80. this.prepareRows(e.data[1]);
  81. this.setState({ 'parsedStreams': (i + 1) });
  82. });
  83. }
  84. prom_1 = prom_1.then(() => {
  85. const prom_2 = new Promise(accept => {
  86. manifestWorker.onmessage = e => accept(e);
  87. manifestWorker.postMessage([ 'getData' ]);
  88. });
  89. return prom_2;
  90. });
  91. prom_1 = prom_1.then(e => {
  92. this.state.rootDirWrapper = new WBRootDirWrapper(e.data[1], e.data[2]);
  93. this.setState({
  94. 'mode': 'browsingReady'
  95. });
  96. this.prepareRows(this.state.rootDirWrapper.listDirectory('.' +
  97. this.props.collectionPath));
  98. });
  99. return prom_1;
  100. });
  101. }
  102. componentWillReceiveProps(nextProps) {
  103. this.props = nextProps;
  104. const { rootDirWrapper, mode } = this.state;
  105. const { collectionPath } = this.props;
  106. if (mode === 'browsingReady') {
  107. const listing = rootDirWrapper.listDirectory('.' + collectionPath);
  108. this.prepareRows(listing);
  109. }
  110. }
  111. prepareRows(listing) {
  112. let { rootDirWrapper, mode } = this.state;
  113. let { collectionPath, page, itemsPerPage, app } = this.props;
  114. let { arvHost, arvToken } = app.state;
  115. const numPages = Math.ceil(listing.length / itemsPerPage);
  116. listing = listing.slice(page * itemsPerPage,
  117. page * itemsPerPage + itemsPerPage);
  118. this.setState({
  119. 'numPages': numPages,
  120. 'rows': listing.map(item => (
  121. (item[0] === 'd') ? [
  122. (<a href={ this.getUrl({ 'collectionPath': collectionPath + '/' + item[1], 'page': 0 }) }>{ item[1] }/</a>),
  123. 'Directory',
  124. null,
  125. (<div></div>)
  126. ] : [
  127. item[1],
  128. 'File',
  129. filesize(item[2]),
  130. ( (mode === 'browsingReady') ? (
  131. <div>
  132. <button class="btn btn-outline-primary mx-1" title="Download"
  133. onclick={ () => {
  134. const file = rootDirWrapper.getFile('.' + collectionPath + '/' + item[1]);
  135. const blob = new Blob([
  136. JSON.stringify([ arvHost, arvToken, item[1], file ])
  137. ]);
  138. const blocksBlobUrl = URL.createObjectURL(blob);
  139. window.open('/download/' + encodeURIComponent(blocksBlobUrl), '_blank');
  140. } }><i class="fas fa-download"></i></button>
  141. <button class="btn btn-outline-primary mx-1" title="View"
  142. onclick={ () => {
  143. alert('Not implemented.')
  144. } }><i class="far fa-eye"></i></button>
  145. </div>
  146. ) : null)
  147. ]
  148. ))
  149. });
  150. }
  151. render({ collectionPath, page }, { manifestReader, rows,
  152. numPages, loaded, total, mode, parsedStreams, totalStreams }) {
  153. return (
  154. <div>
  155. <WBBreadcrumbs items={ ('.' + collectionPath).split('/') } />
  156. { (mode === 'manifestDownload') ?
  157. (
  158. <div class="container-fluid">
  159. <div>Downloading manifest: { filesize(loaded) }</div>
  160. <div class="progress">
  161. <div class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar"
  162. aria-valuenow="100" aria-valuemin="0" aria-valuemax="100" style="width: 100%"></div>
  163. </div>
  164. </div>
  165. ) : (
  166. <div>
  167. { mode === 'manifestParse' ? (
  168. <div class="container-fluid mb-2">
  169. <div>Parsing manifest: { parsedStreams }/{ totalStreams }</div>
  170. <div class="progress">
  171. <div class="progress-bar progress-bar-striped progress-bar-animated bg-success" role="progressbar"
  172. aria-valuenow={ totalStreams } aria-valuemin="0" aria-valuemax={ parsedStreams } style={ 'width: ' + Math.round(parsedStreams * 100 / totalStreams) + '%' }></div>
  173. </div>
  174. </div>
  175. ) : null }
  176. <WBTable columns={ [ 'Name', 'Type', 'Size', 'Actions' ] }
  177. rows={ rows } />
  178. <WBPagination activePage={ page } numPages={ numPages }
  179. getPageUrl={ page => this.getUrl({ 'page': page }) } />
  180. </div>
  181. ) }
  182. </div>
  183. );
  184. }
  185. }
  186. WBCollectionContent.defaultProps = {
  187. 'collectionPath': '',
  188. 'page': 0,
  189. 'itemsPerPage': 20
  190. };
  191. export default WBCollectionContent;