4d750362e3972c339d95ca27f18aff7d3bed82e0
[arvados-workbench2.git] / src / services / collection-service / collection-service.ts
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 import { CommonResourceService } from "../../common/api/common-resource-service";
6 import { CollectionResource } from "../../models/collection";
7 import axios, { AxiosInstance } from "axios";
8 import { KeepService } from "../keep-service/keep-service";
9 import { FilterBuilder } from "../../common/api/filter-builder";
10 import { CollectionFile, createCollectionFile } from "../../models/collection-file";
11 import { parseKeepManifestText, stringifyKeepManifest } from "../collection-files-service/collection-manifest-parser";
12 import * as _ from "lodash";
13 import { KeepManifestStream } from "../../models/keep-manifest";
14
15 export type UploadProgress = (fileId: number, loaded: number, total: number, currentTime: number) => void;
16
17 export class CollectionService extends CommonResourceService<CollectionResource> {
18     constructor(serverApi: AxiosInstance, private keepService: KeepService) {
19         super(serverApi, "collections");
20     }
21
22     private readFile(file: File): Promise<ArrayBuffer> {
23         return new Promise<ArrayBuffer>(resolve => {
24             const reader = new FileReader();
25             reader.onload = () => {
26                 resolve(reader.result as ArrayBuffer);
27             };
28
29             reader.readAsArrayBuffer(file);
30         });
31     }
32
33     private uploadFile(keepServiceHost: string, file: File, fileId: number, onProgress?: UploadProgress): Promise<CollectionFile> {
34         return this.readFile(file).then(content => {
35             return axios.post<string>(keepServiceHost, content, {
36                 headers: {
37                     'Content-Type': 'text/octet-stream'
38                 },
39                 onUploadProgress: (e: ProgressEvent) => {
40                     if (onProgress) {
41                         onProgress(fileId, e.loaded, e.total, Date.now());
42                     }
43                     console.log(`${e.loaded} / ${e.total}`);
44                 }
45             }).then(data => createCollectionFile({
46                 id: data.data,
47                 name: file.name,
48                 size: file.size
49             }));
50         });
51     }
52
53     private async updateManifest(collectionUuid: string, files: CollectionFile[]): Promise<CollectionResource> {
54         const collection = await this.get(collectionUuid);
55         const manifest: KeepManifestStream[] = parseKeepManifestText(collection.manifestText);
56
57         files.forEach(f => {
58             let kms = manifest.find(stream => stream.name === f.path);
59             if (!kms) {
60                 kms = {
61                     files: [],
62                     locators: [],
63                     name: f.path
64                 };
65                 manifest.push(kms);
66             }
67             kms.locators.push(f.id);
68             const len = kms.files.length;
69             const nextPos = len > 0
70                 ? parseInt(kms.files[len - 1].position, 10) + kms.files[len - 1].size
71                 : 0;
72             kms.files.push({
73                 name: f.name,
74                 position: nextPos.toString(),
75                 size: f.size
76             });
77         });
78
79         console.log(manifest);
80
81         const manifestText = stringifyKeepManifest(manifest);
82         const data = { ...collection, manifestText };
83         return this.update(collectionUuid, CommonResourceService.mapKeys(_.snakeCase)(data));
84     }
85
86     uploadFiles(collectionUuid: string, files: File[], onProgress?: UploadProgress): Promise<CollectionResource | never> {
87         const filters = FilterBuilder.create()
88             .addEqual("service_type", "proxy");
89
90         return this.keepService.list({ filters }).then(data => {
91             if (data.items && data.items.length > 0) {
92                 const serviceHost =
93                     (data.items[0].serviceSslFlag ? "https://" : "http://") +
94                     data.items[0].serviceHost +
95                     ":" + data.items[0].servicePort;
96
97                 console.log("serviceHost", serviceHost);
98
99                 const files$ = files.map((f, idx) => this.uploadFile(serviceHost, f, idx, onProgress));
100                 return Promise.all(files$).then(values => {
101                     return this.updateManifest(collectionUuid, values);
102                 });
103             } else {
104                 return Promise.reject("Missing keep service host");
105             }
106         });
107     }
108 }