Merge branch 'master' into 13990-collection-files-service-based-on-webdav
authorMichal Klobukowski <michal.klobukowski@contractors.roche.com>
Fri, 17 Aug 2018 13:47:30 +0000 (15:47 +0200)
committerMichal Klobukowski <michal.klobukowski@contractors.roche.com>
Fri, 17 Aug 2018 13:47:30 +0000 (15:47 +0200)
refs #13990

Arvados-DCO-1.1-Signed-off-by: Michal Klobukowski <michal.klobukowski@contractors.roche.com>

1  2 
src/services/collection-service/collection-service.ts
src/views/workbench/workbench.tsx

index d07ef216ac0c51b7e14dbbccff967642e6c3db22,1c8eb5e9418f75fd9ac04561f44a41cc80eef90d..9feec699e52dfd07070105c75c251d96b3107541
@@@ -2,81 -2,23 +2,81 @@@
  //
  // SPDX-License-Identifier: AGPL-3.0
  
 +import * as _ from "lodash";
  import { CommonResourceService } from "~/common/api/common-resource-service";
  import { CollectionResource } from "~/models/collection";
  import axios, { AxiosInstance } from "axios";
  import { KeepService } from "../keep-service/keep-service";
 +import { WebDAV } from "~/common/webdav";
 +import { AuthService } from "../auth-service/auth-service";
 +import { mapTree, getNodeChildren, getNode, TreeNode } from "../../models/tree";
 +import { getTagValue } from "~/common/xml";
  import { FilterBuilder } from "~/common/api/filter-builder";
 -import { CollectionFile, createCollectionFile } from "~/models/collection-file";
 +import { CollectionFile, createCollectionFile, CollectionFileType, CollectionDirectory, createCollectionDirectory } from '~/models/collection-file';
  import { parseKeepManifestText, stringifyKeepManifest } from "../collection-files-service/collection-manifest-parser";
 -import * as _ from "lodash";
  import { KeepManifestStream } from "~/models/keep-manifest";
 +import { createCollectionFilesTree } from '~/models/collection-file';
  
  export type UploadProgress = (fileId: number, loaded: number, total: number, currentTime: number) => void;
  
  export class CollectionService extends CommonResourceService<CollectionResource> {
 -    constructor(serverApi: AxiosInstance, private keepService: KeepService) {
 +    constructor(serverApi: AxiosInstance, private keepService: KeepService, private webdavClient: WebDAV, private authService: AuthService) {
          super(serverApi, "collections");
      }
  
 +    async files(uuid: string) {
 +        const request = await this.webdavClient.propfind(`/c=${uuid}`);
 +        if (request.responseXML != null) {
 +            const files = this.extractFilesData(request.responseXML);
 +            const tree = createCollectionFilesTree(files);
 +            const sortedTree = mapTree(node => {
 +                const children = getNodeChildren(node.id)(tree).map(id => getNode(id)(tree)) as TreeNode<CollectionDirectory | CollectionFile>[];
 +                children.sort((a, b) =>
 +                    a.value.type !== b.value.type
 +                        ? a.value.type === CollectionFileType.DIRECTORY ? -1 : 1
 +                        : a.value.name.localeCompare(b.value.name)
 +                );
 +                return { ...node, children: children.map(child => child.id) };
 +            })(tree);
 +            return sortedTree;
 +        }
 +        return Promise.reject();
 +    }
 +
 +    async deleteFile(collectionUuid: string, filePath: string) {
 +        return this.webdavClient.delete(`/c=${collectionUuid}${filePath}`);
 +    }
 +
 +    extractFilesData(document: Document) {
 +        const collectionUrlPrefix = /\/c=[0-9a-zA-Z\-]*/;
 +        return Array
 +            .from(document.getElementsByTagName('D:response'))
 +            .slice(1) // omit first element which is collection itself
 +            .map(element => {
 +                const name = getTagValue(element, 'D:displayname', '');
 +                const size = parseInt(getTagValue(element, 'D:getcontentlength', '0'), 10);
 +                const pathname = getTagValue(element, 'D:href', '');
 +                const nameSuffix = `/${name || ''}`;
 +                const directory = pathname
 +                    .replace(collectionUrlPrefix, '')
 +                    .replace(nameSuffix, '');
 +                const href = this.webdavClient.defaults.baseURL + pathname + '?api_token=' + this.authService.getApiToken();
 +
 +                const data = {
 +                    url: href,
 +                    id: `${directory}/${name}`,
 +                    name,
 +                    path: directory,
 +                };
 +
 +                return getTagValue(element, 'D:resourcetype', '')
 +                    ? createCollectionDirectory(data)
 +                    : createCollectionFile({ ...data, size });
 +
 +            });
 +    }
 +
 +
      private readFile(file: File): Promise<ArrayBuffer> {
          return new Promise<ArrayBuffer>(resolve => {
              const reader = new FileReader();
      }
  
      uploadFiles(collectionUuid: string, files: File[], onProgress?: UploadProgress): Promise<CollectionResource | never> {
-         const filters = FilterBuilder.create()
+         const filters = new FilterBuilder()
              .addEqual("service_type", "proxy");
  
-         return this.keepService.list({ filters }).then(data => {
+         return this.keepService.list({ filters: filters.getFilters() }).then(data => {
              if (data.items && data.items.length > 0) {
                  const serviceHost =
                      (data.items[0].serviceSslFlag ? "https://" : "http://") +
index 58641fb36bc5e8fdf629bd2006b009d98660a834,a38afb7ac30e32b8f06f265a1635693aead54ca9..5cf632faf174b655b91b29e456efaceba68d6849
@@@ -42,6 -42,7 +42,7 @@@ import { CollectionPanel } from '../col
  import { loadCollection, loadCollectionTags } from '~/store/collection-panel/collection-panel-action';
  import { getCollectionUrl } from '~/models/collection';
  import { UpdateCollectionDialog } from '~/views-components/update-collection-dialog/update-collection-dialog.';
+ import { UpdateProjectDialog } from '~/views-components/update-project-dialog/update-project-dialog';
  import { AuthService } from "~/services/auth-service/auth-service";
  import { RenameFileDialog } from '~/views-components/rename-file-dialog/rename-file-dialog';
  import { FileRemoveDialog } from '~/views-components/file-remove-dialog/file-remove-dialog';
@@@ -245,6 -246,7 +246,7 @@@ export const Workbench = withStyles(sty
                          <FileRemoveDialog />
                          <MultipleFilesRemoveDialog />
                          <UpdateCollectionDialog />
+                         <UpdateProjectDialog />
                          <CurrentTokenDialog
                              currentToken={this.props.currentToken}
                              open={this.state.isCurrentTokenDialogOpen}
  
              renderCollectionPanel = (props: RouteComponentProps<{ id: string }>) => <CollectionPanel
                  onItemRouteChange={(collectionId) => {
 -                    this.props.dispatch<any>(loadCollection(collectionId, ResourceKind.COLLECTION));
 +                    this.props.dispatch<any>(loadCollection(collectionId));
                      this.props.dispatch<any>(loadCollectionTags(collectionId));
                  }}
                  onContextMenu={(event, item) => {
                  onItemDoubleClick={item => {
                      switch (item.kind) {
                          case ResourceKind.COLLECTION:
 -                            this.props.dispatch(loadCollection(item.uuid, item.kind as ResourceKind));
 +                            this.props.dispatch(loadCollection(item.uuid));
                              this.props.dispatch(push(getCollectionUrl(item.uuid)));
                          default:
                              this.props.dispatch(setProjectItem(item.uuid, ItemMode.ACTIVE));
                  onItemDoubleClick={item => {
                      switch (item.kind) {
                          case ResourceKind.COLLECTION:
 -                            this.props.dispatch(loadCollection(item.uuid, item.kind as ResourceKind));
 +                            this.props.dispatch(loadCollection(item.uuid));
                              this.props.dispatch(push(getCollectionUrl(item.uuid)));
                          default:
                              this.props.dispatch(loadDetails(item.uuid, ResourceKind.PROJECT));