@milaboratories/pl-drivers 1.2.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -0
- package/dist/clients/download.d.ts +30 -0
- package/dist/clients/download.d.ts.map +1 -0
- package/dist/clients/helpers.d.ts +14 -0
- package/dist/clients/helpers.d.ts.map +1 -0
- package/dist/clients/logs.d.ts +26 -0
- package/dist/clients/logs.d.ts.map +1 -0
- package/dist/clients/ls_api.d.ts +13 -0
- package/dist/clients/ls_api.d.ts.map +1 -0
- package/dist/clients/progress.d.ts +25 -0
- package/dist/clients/progress.d.ts.map +1 -0
- package/dist/clients/upload.d.ts +38 -0
- package/dist/clients/upload.d.ts.map +1 -0
- package/dist/drivers/download_and_logs_blob.d.ts +106 -0
- package/dist/drivers/download_and_logs_blob.d.ts.map +1 -0
- package/dist/drivers/download_url.d.ts +70 -0
- package/dist/drivers/download_url.d.ts.map +1 -0
- package/dist/drivers/helpers/files_cache.d.ts +28 -0
- package/dist/drivers/helpers/files_cache.d.ts.map +1 -0
- package/dist/drivers/helpers/helpers.d.ts +34 -0
- package/dist/drivers/helpers/helpers.d.ts.map +1 -0
- package/dist/drivers/helpers/ls_list_entry.d.ts +49 -0
- package/dist/drivers/helpers/ls_list_entry.d.ts.map +1 -0
- package/dist/drivers/helpers/ls_storage_entry.d.ts +25 -0
- package/dist/drivers/helpers/ls_storage_entry.d.ts.map +1 -0
- package/dist/drivers/helpers/polling_ops.d.ts +8 -0
- package/dist/drivers/helpers/polling_ops.d.ts.map +1 -0
- package/dist/drivers/helpers/test_helpers.d.ts +2 -0
- package/dist/drivers/helpers/test_helpers.d.ts.map +1 -0
- package/dist/drivers/logs.d.ts +29 -0
- package/dist/drivers/logs.d.ts.map +1 -0
- package/dist/drivers/logs_stream.d.ts +50 -0
- package/dist/drivers/logs_stream.d.ts.map +1 -0
- package/dist/drivers/ls.d.ts +30 -0
- package/dist/drivers/ls.d.ts.map +1 -0
- package/dist/drivers/upload.d.ts +87 -0
- package/dist/drivers/upload.d.ts.map +1 -0
- package/dist/helpers/download.d.ts +15 -0
- package/dist/helpers/download.d.ts.map +1 -0
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4627 -0
- package/dist/index.js.map +1 -0
- package/dist/proto/github.com/milaboratory/pl/controllers/shared/grpc/downloadapi/protocol.client.d.ts +36 -0
- package/dist/proto/github.com/milaboratory/pl/controllers/shared/grpc/downloadapi/protocol.client.d.ts.map +1 -0
- package/dist/proto/github.com/milaboratory/pl/controllers/shared/grpc/downloadapi/protocol.d.ts +103 -0
- package/dist/proto/github.com/milaboratory/pl/controllers/shared/grpc/downloadapi/protocol.d.ts.map +1 -0
- package/dist/proto/github.com/milaboratory/pl/controllers/shared/grpc/lsapi/protocol.client.d.ts +42 -0
- package/dist/proto/github.com/milaboratory/pl/controllers/shared/grpc/lsapi/protocol.client.d.ts.map +1 -0
- package/dist/proto/github.com/milaboratory/pl/controllers/shared/grpc/lsapi/protocol.d.ts +165 -0
- package/dist/proto/github.com/milaboratory/pl/controllers/shared/grpc/lsapi/protocol.d.ts.map +1 -0
- package/dist/proto/github.com/milaboratory/pl/controllers/shared/grpc/progressapi/protocol.client.d.ts +44 -0
- package/dist/proto/github.com/milaboratory/pl/controllers/shared/grpc/progressapi/protocol.client.d.ts.map +1 -0
- package/dist/proto/github.com/milaboratory/pl/controllers/shared/grpc/progressapi/protocol.d.ts +171 -0
- package/dist/proto/github.com/milaboratory/pl/controllers/shared/grpc/progressapi/protocol.d.ts.map +1 -0
- package/dist/proto/github.com/milaboratory/pl/controllers/shared/grpc/streamingapi/protocol.client.d.ts +122 -0
- package/dist/proto/github.com/milaboratory/pl/controllers/shared/grpc/streamingapi/protocol.client.d.ts.map +1 -0
- package/dist/proto/github.com/milaboratory/pl/controllers/shared/grpc/streamingapi/protocol.d.ts +315 -0
- package/dist/proto/github.com/milaboratory/pl/controllers/shared/grpc/streamingapi/protocol.d.ts.map +1 -0
- package/dist/proto/github.com/milaboratory/pl/controllers/shared/grpc/uploadapi/protocol.client.d.ts +98 -0
- package/dist/proto/github.com/milaboratory/pl/controllers/shared/grpc/uploadapi/protocol.client.d.ts.map +1 -0
- package/dist/proto/github.com/milaboratory/pl/controllers/shared/grpc/uploadapi/protocol.d.ts +337 -0
- package/dist/proto/github.com/milaboratory/pl/controllers/shared/grpc/uploadapi/protocol.d.ts.map +1 -0
- package/dist/proto/google/api/http.d.ts +451 -0
- package/dist/proto/google/api/http.d.ts.map +1 -0
- package/dist/proto/google/protobuf/descriptor.d.ts +1646 -0
- package/dist/proto/google/protobuf/descriptor.d.ts.map +1 -0
- package/dist/proto/google/protobuf/duration.d.ts +106 -0
- package/dist/proto/google/protobuf/duration.d.ts.map +1 -0
- package/dist/proto/google/protobuf/timestamp.d.ts +151 -0
- package/dist/proto/google/protobuf/timestamp.d.ts.map +1 -0
- package/package.json +47 -0
- package/src/clients/download.test.ts +45 -0
- package/src/clients/download.ts +106 -0
- package/src/clients/helpers.ts +84 -0
- package/src/clients/logs.ts +68 -0
- package/src/clients/ls_api.ts +34 -0
- package/src/clients/progress.ts +86 -0
- package/src/clients/upload.test.ts +30 -0
- package/src/clients/upload.ts +199 -0
- package/src/drivers/download_and_logs_blob.ts +801 -0
- package/src/drivers/download_blob.test.ts +223 -0
- package/src/drivers/download_url.test.ts +90 -0
- package/src/drivers/download_url.ts +314 -0
- package/src/drivers/helpers/files_cache.test.ts +79 -0
- package/src/drivers/helpers/files_cache.ts +74 -0
- package/src/drivers/helpers/helpers.ts +136 -0
- package/src/drivers/helpers/ls_list_entry.test.ts +57 -0
- package/src/drivers/helpers/ls_list_entry.ts +152 -0
- package/src/drivers/helpers/ls_storage_entry.ts +135 -0
- package/src/drivers/helpers/polling_ops.ts +7 -0
- package/src/drivers/helpers/test_helpers.ts +5 -0
- package/src/drivers/logs.test.ts +337 -0
- package/src/drivers/logs.ts +214 -0
- package/src/drivers/logs_stream.ts +399 -0
- package/src/drivers/ls.test.ts +90 -0
- package/src/drivers/ls.ts +147 -0
- package/src/drivers/upload.test.ts +454 -0
- package/src/drivers/upload.ts +499 -0
- package/src/helpers/download.ts +43 -0
- package/src/index.ts +15 -0
- package/src/proto/github.com/milaboratory/pl/controllers/shared/grpc/downloadapi/protocol.client.ts +60 -0
- package/src/proto/github.com/milaboratory/pl/controllers/shared/grpc/downloadapi/protocol.ts +442 -0
- package/src/proto/github.com/milaboratory/pl/controllers/shared/grpc/lsapi/protocol.client.ts +63 -0
- package/src/proto/github.com/milaboratory/pl/controllers/shared/grpc/lsapi/protocol.ts +503 -0
- package/src/proto/github.com/milaboratory/pl/controllers/shared/grpc/progressapi/protocol.client.ts +84 -0
- package/src/proto/github.com/milaboratory/pl/controllers/shared/grpc/progressapi/protocol.ts +697 -0
- package/src/proto/github.com/milaboratory/pl/controllers/shared/grpc/streamingapi/protocol.client.ts +212 -0
- package/src/proto/github.com/milaboratory/pl/controllers/shared/grpc/streamingapi/protocol.ts +1036 -0
- package/src/proto/github.com/milaboratory/pl/controllers/shared/grpc/uploadapi/protocol.client.ts +170 -0
- package/src/proto/github.com/milaboratory/pl/controllers/shared/grpc/uploadapi/protocol.ts +1201 -0
- package/src/proto/google/api/http.ts +838 -0
- package/src/proto/google/protobuf/descriptor.ts +5173 -0
- package/src/proto/google/protobuf/duration.ts +272 -0
- package/src/proto/google/protobuf/timestamp.ts +354 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { CallersCounter, mapEntries, mapGet } from '@milaboratories/ts-helpers';
|
|
2
|
+
|
|
3
|
+
type PathLike = string;
|
|
4
|
+
|
|
5
|
+
export interface CachedFile {
|
|
6
|
+
sizeBytes: number;
|
|
7
|
+
path: PathLike;
|
|
8
|
+
counter: CallersCounter;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Holds counters of how many callers need the file.
|
|
12
|
+
* If some counters become zero and a cache size exceeds a soft limit,
|
|
13
|
+
* remove not needed blobs one by one.
|
|
14
|
+
* If all the files are needed, do nothing. */
|
|
15
|
+
export class FilesCache<T extends CachedFile> {
|
|
16
|
+
private cache: Map<PathLike, T> = new Map();
|
|
17
|
+
private totalSizeBytes: number = 0;
|
|
18
|
+
|
|
19
|
+
constructor(private readonly softSizeBytes: number) {}
|
|
20
|
+
|
|
21
|
+
existsFile(path: PathLike): boolean {
|
|
22
|
+
return this.cache.get(path) != undefined;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
getFile(path: PathLike, callerId: string): T | undefined {
|
|
26
|
+
const file = this.cache.get(path);
|
|
27
|
+
if (file != undefined) {
|
|
28
|
+
file.counter.inc(callerId);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return file;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Decrements a counter in a cache and if we exceeds
|
|
35
|
+
* a soft limit, removes files with zero counters. */
|
|
36
|
+
removeFile(path: PathLike, callerId: string): T[] {
|
|
37
|
+
mapGet(this.cache, path).counter.dec(callerId);
|
|
38
|
+
return this.toDelete();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Returns what results should be deleted to comply with the soft limit. */
|
|
42
|
+
toDelete(): T[] {
|
|
43
|
+
if (this.totalSizeBytes <= this.softSizeBytes) return [];
|
|
44
|
+
|
|
45
|
+
const result: T[] = [];
|
|
46
|
+
let freedBytes = 0;
|
|
47
|
+
|
|
48
|
+
mapEntries(this.cache)
|
|
49
|
+
.filter(([_, file]: [string, T]) => file.counter.isZero())
|
|
50
|
+
.forEach(([path, _]) => {
|
|
51
|
+
if (this.totalSizeBytes - freedBytes <= this.softSizeBytes) return;
|
|
52
|
+
const file = mapGet(this.cache, path);
|
|
53
|
+
freedBytes += file.sizeBytes;
|
|
54
|
+
result.push(file);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
addCache(file: T, callerId: string) {
|
|
61
|
+
const created = this.cache.get(file.path) == undefined;
|
|
62
|
+
this.cache.set(file.path, file);
|
|
63
|
+
file.counter.inc(callerId);
|
|
64
|
+
|
|
65
|
+
if (file.sizeBytes < 0) throw new Error(`empty sizeBytes: ${file}`);
|
|
66
|
+
|
|
67
|
+
if (created) this.totalSizeBytes += file.sizeBytes;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
removeCache(file: T) {
|
|
71
|
+
this.cache.delete(file.path);
|
|
72
|
+
this.totalSizeBytes -= file.sizeBytes;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import type { RpcOptions } from '@protobuf-ts/runtime-rpc';
|
|
2
|
+
import { ClientLogs } from '../../clients/logs';
|
|
3
|
+
import {
|
|
4
|
+
PlClient,
|
|
5
|
+
ResourceId,
|
|
6
|
+
BasicResourceData,
|
|
7
|
+
isNullResourceId,
|
|
8
|
+
valErr,
|
|
9
|
+
getField
|
|
10
|
+
} from '@milaboratories/pl-client';
|
|
11
|
+
import { scheduler } from 'node:timers/promises';
|
|
12
|
+
import { ResourceInfo } from '@milaboratories/pl-tree';
|
|
13
|
+
|
|
14
|
+
// TODO: remove this when we switch to refreshState.
|
|
15
|
+
|
|
16
|
+
/** It's an Updater but for tasks that happens in a while loop with sleeping between. */
|
|
17
|
+
export class LongUpdater {
|
|
18
|
+
private updater: Updater;
|
|
19
|
+
|
|
20
|
+
constructor(
|
|
21
|
+
private readonly onUpdate: () => Promise<boolean>,
|
|
22
|
+
private readonly sleepMs: number
|
|
23
|
+
) {
|
|
24
|
+
this.updater = new Updater(async () => {
|
|
25
|
+
while (true) {
|
|
26
|
+
const done = await this.onUpdate();
|
|
27
|
+
if (done) return;
|
|
28
|
+
await scheduler.wait(this.sleepMs);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
schedule = () => this.updater.schedule();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Updater incorporates a pattern when someone wants to run a callback
|
|
37
|
+
* that updates something only when it's not already running. */
|
|
38
|
+
export class Updater {
|
|
39
|
+
private updating: Promise<void> | undefined;
|
|
40
|
+
|
|
41
|
+
constructor(private readonly onUpdate: () => Promise<void>) {}
|
|
42
|
+
|
|
43
|
+
schedule() {
|
|
44
|
+
if (this.updating == undefined) {
|
|
45
|
+
this.updating = (async () => {
|
|
46
|
+
try {
|
|
47
|
+
await this.onUpdate();
|
|
48
|
+
} catch (e) {
|
|
49
|
+
console.log(`error while updating in Updater: ${e}`);
|
|
50
|
+
} finally {
|
|
51
|
+
this.updating = undefined;
|
|
52
|
+
}
|
|
53
|
+
})();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// TODO: remove all the code below to the computable that calculates Mixcr logs.
|
|
59
|
+
|
|
60
|
+
export async function getStream(
|
|
61
|
+
client: PlClient,
|
|
62
|
+
streamManagerId: ResourceId
|
|
63
|
+
): Promise<BasicResourceData | undefined> {
|
|
64
|
+
return client.withReadTx('LogsDriverGetStream', async (tx) => {
|
|
65
|
+
const sm = await tx.getResourceData(streamManagerId, true);
|
|
66
|
+
const stream = await valErr(tx, getField(sm, 'stream'));
|
|
67
|
+
if (stream.error != '') {
|
|
68
|
+
throw new Error(`while getting stream: ${stream.error}`);
|
|
69
|
+
}
|
|
70
|
+
if (isNullResourceId(stream.valueId)) return undefined;
|
|
71
|
+
|
|
72
|
+
return await tx.getResourceData(stream.valueId, false);
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export type MixcrProgressResponse =
|
|
77
|
+
| { found: false }
|
|
78
|
+
| ({ found: true } & MixcrProgressLine);
|
|
79
|
+
|
|
80
|
+
export type MixcrProgressLine = {
|
|
81
|
+
stage: string; // Building pre-clones from tag groups
|
|
82
|
+
progress: string; // 35.3%
|
|
83
|
+
eta: string; // ETA: 00:00:07
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/** Is set by a template code.
|
|
87
|
+
* Mixcr adds this prefix to every log line that contains a progress. */
|
|
88
|
+
const mixcrProgressPrefix = '8C7#F1328%9E089B3D22';
|
|
89
|
+
const mixcrProgressRegex =
|
|
90
|
+
/(?<stage>.*):\s*(?<progress>[\d.]+%)\s.*(?<eta>ETA:.*)/g;
|
|
91
|
+
|
|
92
|
+
export function lineToProgress(line: string): MixcrProgressLine | undefined {
|
|
93
|
+
const noPrefix = line.replace(mixcrProgressPrefix, '');
|
|
94
|
+
const parsed = noPrefix.match(mixcrProgressRegex);
|
|
95
|
+
|
|
96
|
+
if (parsed == null || parsed.length != 4) {
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const [_, stage, progress, eta] = parsed;
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
stage, // For example, 'Building pre-clones from tag groups'
|
|
104
|
+
progress, // 35.3%
|
|
105
|
+
eta // ETA: 00:00:07
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export async function mixcrProgressFromLogs(
|
|
110
|
+
rInfo: ResourceInfo,
|
|
111
|
+
client: ClientLogs,
|
|
112
|
+
options?: RpcOptions
|
|
113
|
+
): Promise<MixcrProgressResponse> {
|
|
114
|
+
const lastLines = await client.lastLines(
|
|
115
|
+
rInfo,
|
|
116
|
+
1,
|
|
117
|
+
0n,
|
|
118
|
+
mixcrProgressPrefix,
|
|
119
|
+
options
|
|
120
|
+
);
|
|
121
|
+
if (lastLines.data == null || lastLines.data.length == 0) {
|
|
122
|
+
return { found: false };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const line = lastLines.data.toString().split(/\r?\n/)[0];
|
|
126
|
+
if (line == undefined) {
|
|
127
|
+
return { found: false };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const progress = lineToProgress(line);
|
|
131
|
+
if (progress === undefined) {
|
|
132
|
+
return { found: false };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return { found: true, ...progress };
|
|
136
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ConsoleLoggerAdapter,
|
|
3
|
+
HmacSha256Signer
|
|
4
|
+
} from '@milaboratories/ts-helpers';
|
|
5
|
+
import { fromFileHandle, toFileHandle, toListItem } from './ls_list_entry';
|
|
6
|
+
import type { Dirent, Stats } from 'node:fs';
|
|
7
|
+
|
|
8
|
+
test('toFileHandle should ok when encode data for UploadBlob', () => {
|
|
9
|
+
const signer = new HmacSha256Signer('abc');
|
|
10
|
+
|
|
11
|
+
const handle = toFileHandle({
|
|
12
|
+
storageName: 'library',
|
|
13
|
+
signer,
|
|
14
|
+
remote: false,
|
|
15
|
+
item: {
|
|
16
|
+
directory: 'C:\\programFiles\\',
|
|
17
|
+
name: 'file.txt',
|
|
18
|
+
size: 20n,
|
|
19
|
+
isDir: false,
|
|
20
|
+
fullName: 'C:\\programFiles\\file.txt',
|
|
21
|
+
lastModified: { seconds: 150n, nanos: 260 }
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
const got = fromFileHandle(handle);
|
|
25
|
+
|
|
26
|
+
expect(got.modificationTime).toEqual('150');
|
|
27
|
+
expect(got.localPath).toEqual('C:\\programFiles\\file.txt');
|
|
28
|
+
expect(got.pathSignature).not.toHaveLength(0);
|
|
29
|
+
expect(got.sizeBytes).toEqual('20');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('toListItem should ok', () => {
|
|
33
|
+
const got = toListItem(new ConsoleLoggerAdapter(), {
|
|
34
|
+
fullName: 'C:\\programFiles\\file.txt',
|
|
35
|
+
dirent: {
|
|
36
|
+
isFile: () => true,
|
|
37
|
+
isDirectory: () => false,
|
|
38
|
+
name: 'file.txt'
|
|
39
|
+
} as Dirent,
|
|
40
|
+
directory: 'C:\\programFiles\\',
|
|
41
|
+
stat: {
|
|
42
|
+
mtimeMs: 150000,
|
|
43
|
+
size: 20
|
|
44
|
+
} as Stats
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
expect(got).toMatchObject({
|
|
48
|
+
isDir: false,
|
|
49
|
+
name: 'file.txt',
|
|
50
|
+
fullName: 'C:\\programFiles\\file.txt',
|
|
51
|
+
lastModified: {
|
|
52
|
+
seconds: 150n,
|
|
53
|
+
nanos: 0
|
|
54
|
+
},
|
|
55
|
+
size: 20n
|
|
56
|
+
});
|
|
57
|
+
});
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { MiLogger, notEmpty, Signer } from '@milaboratories/ts-helpers';
|
|
2
|
+
import * as sdk from '@milaboratories/pl-model-common';
|
|
3
|
+
import { Timestamp } from '../../proto/google/protobuf/timestamp';
|
|
4
|
+
import { Dirent, Stats } from 'node:fs';
|
|
5
|
+
|
|
6
|
+
/** A duck-typing interface for grpc results. */
|
|
7
|
+
export interface ListResponse {
|
|
8
|
+
items: ListItem[];
|
|
9
|
+
delimiter: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface ListItem {
|
|
13
|
+
isDir: boolean;
|
|
14
|
+
name: string;
|
|
15
|
+
fullName: string;
|
|
16
|
+
lastModified?: Timestamp;
|
|
17
|
+
size: bigint;
|
|
18
|
+
directory: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** */
|
|
22
|
+
export function toLsEntries(info: {
|
|
23
|
+
storageName: string;
|
|
24
|
+
list: ListResponse;
|
|
25
|
+
signer: Signer;
|
|
26
|
+
remote: boolean;
|
|
27
|
+
}): sdk.ListFilesResult {
|
|
28
|
+
const parent =
|
|
29
|
+
info.list.items.length > 0 ? info.list.items[0]?.directory : undefined;
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
parent: parent,
|
|
33
|
+
entries: info.list.items.map((item) => toLsEntry(item, info))
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function toLsEntry(
|
|
38
|
+
item: ListItem,
|
|
39
|
+
info: {
|
|
40
|
+
storageName: string;
|
|
41
|
+
list: ListResponse;
|
|
42
|
+
signer: Signer;
|
|
43
|
+
remote: boolean;
|
|
44
|
+
}
|
|
45
|
+
): sdk.LsEntry {
|
|
46
|
+
if (item.isDir)
|
|
47
|
+
return {
|
|
48
|
+
type: 'dir',
|
|
49
|
+
name: item.name,
|
|
50
|
+
fullPath: item.fullName
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
type: 'file',
|
|
55
|
+
name: item.name,
|
|
56
|
+
fullPath: item.fullName,
|
|
57
|
+
handle: toFileHandle({ item: item, ...info })
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function toFileHandle(info: {
|
|
62
|
+
storageName: string;
|
|
63
|
+
item: ListItem;
|
|
64
|
+
signer: Signer;
|
|
65
|
+
remote: boolean;
|
|
66
|
+
}): sdk.ImportFileHandle {
|
|
67
|
+
if (info.remote) {
|
|
68
|
+
return createIndexHandle(info);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return createUploadHandle(
|
|
72
|
+
info.item.fullName,
|
|
73
|
+
info.signer,
|
|
74
|
+
info.item.size,
|
|
75
|
+
notEmpty(info.item.lastModified).seconds
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export type UploadHandleData = {
|
|
80
|
+
/** Local file path, to take data for upload */
|
|
81
|
+
localPath: string;
|
|
82
|
+
/** Path signature, to check this data was generated by us */
|
|
83
|
+
pathSignature: string;
|
|
84
|
+
/** File size in bytes */
|
|
85
|
+
sizeBytes: string;
|
|
86
|
+
/** Modification time unix timestamp in seconds */
|
|
87
|
+
modificationTime: string;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
function createIndexHandle(info: {
|
|
91
|
+
storageName: string;
|
|
92
|
+
item: ListItem;
|
|
93
|
+
}): sdk.ImportFileHandleIndex {
|
|
94
|
+
const data = encodeURIComponent(
|
|
95
|
+
JSON.stringify({
|
|
96
|
+
storageId: info.storageName,
|
|
97
|
+
path: info.item.fullName
|
|
98
|
+
})
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
return `index://index/${data}`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function createUploadHandle(
|
|
105
|
+
localPath: string,
|
|
106
|
+
signer: Signer,
|
|
107
|
+
sizeBytes: bigint,
|
|
108
|
+
modificationTimeSeconds: bigint
|
|
109
|
+
): sdk.ImportFileHandleUpload {
|
|
110
|
+
const data: UploadHandleData = {
|
|
111
|
+
localPath,
|
|
112
|
+
pathSignature: signer.sign(localPath),
|
|
113
|
+
sizeBytes: String(sizeBytes),
|
|
114
|
+
modificationTime: String(modificationTimeSeconds)
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
return `upload://upload/${encodeURIComponent(JSON.stringify(data))}` as sdk.ImportFileHandleUpload;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function fromFileHandle(handle: sdk.ImportFileHandle) {
|
|
121
|
+
const url = new URL(handle);
|
|
122
|
+
return JSON.parse(decodeURIComponent(url.pathname.substring(1)));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function toListItem(
|
|
126
|
+
logger: MiLogger,
|
|
127
|
+
info: {
|
|
128
|
+
directory: string;
|
|
129
|
+
fullName: string;
|
|
130
|
+
dirent: Dirent;
|
|
131
|
+
stat: Stats;
|
|
132
|
+
}
|
|
133
|
+
): ListItem | undefined {
|
|
134
|
+
if (!(info.dirent.isFile() || info.dirent.isDirectory())) {
|
|
135
|
+
logger.warn(
|
|
136
|
+
`tried to get non-dir and non-file ${info.dirent.name}, skip it`
|
|
137
|
+
);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
directory: info.directory,
|
|
143
|
+
isDir: info.dirent.isDirectory(),
|
|
144
|
+
name: info.dirent.name,
|
|
145
|
+
fullName: info.fullName,
|
|
146
|
+
lastModified: {
|
|
147
|
+
seconds: BigInt(Math.floor(info.stat.mtimeMs / 1000)),
|
|
148
|
+
nanos: 0
|
|
149
|
+
},
|
|
150
|
+
size: BigInt(info.stat.size)
|
|
151
|
+
};
|
|
152
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import * as sdk from '@milaboratories/pl-model-common';
|
|
2
|
+
import {
|
|
3
|
+
bigintToResourceId,
|
|
4
|
+
ResourceId,
|
|
5
|
+
ResourceType
|
|
6
|
+
} from '@milaboratories/pl-client';
|
|
7
|
+
import { assertNever } from '@milaboratories/ts-helpers';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Converts local and remote storages to StorageEntries.
|
|
11
|
+
*/
|
|
12
|
+
export function toStorageEntry(
|
|
13
|
+
locals: Record<string, string>,
|
|
14
|
+
remotes: Record<string, ResourceId>
|
|
15
|
+
): sdk.StorageEntry[] {
|
|
16
|
+
const localEntries = Object.entries(locals).map(localToEntry);
|
|
17
|
+
const remoteEntries = Object.entries(remotes).map(remoteToEntry);
|
|
18
|
+
|
|
19
|
+
return localEntries.concat(remoteEntries);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type StorageHandleData =
|
|
23
|
+
| RemoteStorageHandleData
|
|
24
|
+
| LocalStorageHandleData;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Gets a storage handle and gives an underlying data from it.
|
|
28
|
+
*/
|
|
29
|
+
export function fromStorageHandle(
|
|
30
|
+
handle: sdk.StorageHandle
|
|
31
|
+
): StorageHandleData {
|
|
32
|
+
if (isRemoteStorageHandle(handle)) {
|
|
33
|
+
return fromRemoteHandle(handle);
|
|
34
|
+
} else if (isLocalStorageHandle(handle)) {
|
|
35
|
+
return fromLocalHandle(handle);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
assertNever(handle);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
//
|
|
42
|
+
// Local storage:
|
|
43
|
+
//
|
|
44
|
+
|
|
45
|
+
export type LocalStorageHandleData = {
|
|
46
|
+
remote: false;
|
|
47
|
+
name: string;
|
|
48
|
+
path: string;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
function localToEntry([name, path]: [string, string]): sdk.StorageEntry {
|
|
52
|
+
return {
|
|
53
|
+
name: name,
|
|
54
|
+
handle: toLocalHandle(name, path),
|
|
55
|
+
initialFullPath: path
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const localHandleRegex = /^local:\/\/(?<name>.*)\/(?<path>.*)$/;
|
|
60
|
+
|
|
61
|
+
export function isLocalStorageHandle(
|
|
62
|
+
handle: sdk.StorageHandle
|
|
63
|
+
): handle is sdk.StorageHandleLocal {
|
|
64
|
+
return localHandleRegex.test(handle);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function toLocalHandle(name: string, path: string): sdk.StorageHandleLocal {
|
|
68
|
+
return `local://${name}/${encodeURIComponent(path)}`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function fromLocalHandle(handle: string): LocalStorageHandleData {
|
|
72
|
+
const parsed = handle.match(localHandleRegex);
|
|
73
|
+
if (parsed == null)
|
|
74
|
+
throw new Error(`Local list handle wasn't parsed: ${handle}`);
|
|
75
|
+
|
|
76
|
+
const { name, path } = parsed.groups!;
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
path: decodeURIComponent(path),
|
|
80
|
+
name,
|
|
81
|
+
remote: false
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
//
|
|
86
|
+
// Remote storage:
|
|
87
|
+
//
|
|
88
|
+
|
|
89
|
+
export type RemoteStorageHandleData = {
|
|
90
|
+
remote: true;
|
|
91
|
+
name: string;
|
|
92
|
+
id: ResourceId;
|
|
93
|
+
type: ResourceType;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
function remoteToEntry([name, rId]: [string, ResourceId]): sdk.StorageEntry {
|
|
97
|
+
return {
|
|
98
|
+
name: name,
|
|
99
|
+
handle: toRemoteHandle(name, rId),
|
|
100
|
+
initialFullPath: ''
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const remoteHandleRegex = /^remote:\/\/(?<name>.*)\/(?<resourceId>.*)$/;
|
|
105
|
+
|
|
106
|
+
export function isRemoteStorageHandle(
|
|
107
|
+
handle: sdk.StorageHandle
|
|
108
|
+
): handle is sdk.StorageHandleRemote {
|
|
109
|
+
return remoteHandleRegex.test(handle);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function toRemoteHandle(
|
|
113
|
+
name: string,
|
|
114
|
+
rId: ResourceId
|
|
115
|
+
): sdk.StorageHandleRemote {
|
|
116
|
+
return `remote://${name}/${BigInt(rId)}`;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function fromRemoteHandle(handle: string): RemoteStorageHandleData {
|
|
120
|
+
const parsed = handle.match(remoteHandleRegex);
|
|
121
|
+
if (parsed == null)
|
|
122
|
+
throw new Error(`Remote list handle wasn't parsed: ${handle}`);
|
|
123
|
+
const { name, resourceId } = parsed.groups!;
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
id: bigintToResourceId(BigInt(resourceId)),
|
|
127
|
+
type: storageType(name),
|
|
128
|
+
name,
|
|
129
|
+
remote: true
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function storageType(name: string): ResourceType {
|
|
134
|
+
return { name: `LS/${name}`, version: '1' };
|
|
135
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Generic settings for drivers that perform polling */
|
|
2
|
+
export type PollingOps = {
|
|
3
|
+
/** How frequent the driver should update exposed states from the backend. */
|
|
4
|
+
pollingInterval: number;
|
|
5
|
+
/** For how long to continue polling after the last derived computable value access. */
|
|
6
|
+
stopPollingDelay: number;
|
|
7
|
+
};
|