@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,399 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ChangeSource,
|
|
3
|
+
Computable,
|
|
4
|
+
ComputableCtx,
|
|
5
|
+
PollingComputableHooks,
|
|
6
|
+
Watcher
|
|
7
|
+
} from '@milaboratories/computable';
|
|
8
|
+
import { ResourceId } from '@milaboratories/pl-client';
|
|
9
|
+
import { asyncPool, CallersCounter } from '@milaboratories/ts-helpers';
|
|
10
|
+
import { ClientLogs } from '../clients/logs';
|
|
11
|
+
import { randomUUID } from 'node:crypto';
|
|
12
|
+
import {
|
|
13
|
+
PlTreeEntry,
|
|
14
|
+
ResourceInfo,
|
|
15
|
+
treeEntryToResourceInfo
|
|
16
|
+
} from '@milaboratories/pl-tree';
|
|
17
|
+
import { dataToHandle, handleToData, isLiveLogHandle } from './logs';
|
|
18
|
+
import { scheduler } from 'node:timers/promises';
|
|
19
|
+
import { StreamingAPI_Response } from '../proto/github.com/milaboratory/pl/controllers/shared/grpc/streamingapi/protocol';
|
|
20
|
+
import * as sdk from '@milaboratories/pl-model-common';
|
|
21
|
+
import { PollingOps } from './helpers/polling_ops';
|
|
22
|
+
|
|
23
|
+
export type LogsStreamDriverOps = PollingOps & {
|
|
24
|
+
/** Max number of concurrent requests to log streaming backend while calculating computable states */
|
|
25
|
+
nConcurrentGetLogs: number;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export class LogsStreamDriver implements sdk.LogsDriver {
|
|
29
|
+
/** Holds a map of StreamManager Resource Id to all logs of this stream. */
|
|
30
|
+
private readonly idToLastLines: Map<ResourceId, LogGetter> = new Map();
|
|
31
|
+
|
|
32
|
+
/** Holds a map of StreamManager Resource Id to the last log line of this stream. */
|
|
33
|
+
private readonly idToProgressLog: Map<ResourceId, LogGetter> = new Map();
|
|
34
|
+
|
|
35
|
+
/** Holds a map of StreamManager Resource Id to log id smart object. */
|
|
36
|
+
private readonly hooks: PollingComputableHooks;
|
|
37
|
+
|
|
38
|
+
constructor(
|
|
39
|
+
private readonly clientLogs: ClientLogs,
|
|
40
|
+
private readonly opts: LogsStreamDriverOps = {
|
|
41
|
+
nConcurrentGetLogs: 10,
|
|
42
|
+
pollingInterval: 1000,
|
|
43
|
+
stopPollingDelay: 1000
|
|
44
|
+
}
|
|
45
|
+
) {
|
|
46
|
+
this.hooks = new PollingComputableHooks(
|
|
47
|
+
() => this.startUpdating(),
|
|
48
|
+
() => this.stopUpdating(),
|
|
49
|
+
{ stopDebounce: opts.stopPollingDelay },
|
|
50
|
+
(resolve, reject) => this.scheduleOnNextState(resolve, reject)
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
getLastLogs(
|
|
55
|
+
res: ResourceInfo | PlTreeEntry,
|
|
56
|
+
lines: number
|
|
57
|
+
): Computable<string | undefined>;
|
|
58
|
+
getLastLogs(
|
|
59
|
+
res: ResourceInfo | PlTreeEntry,
|
|
60
|
+
lines: number,
|
|
61
|
+
ctx: ComputableCtx
|
|
62
|
+
): Computable<string | undefined>;
|
|
63
|
+
getLastLogs(
|
|
64
|
+
res: ResourceInfo | PlTreeEntry,
|
|
65
|
+
lines: number,
|
|
66
|
+
ctx?: ComputableCtx
|
|
67
|
+
): Computable<string | undefined> | string | undefined {
|
|
68
|
+
if (ctx == undefined)
|
|
69
|
+
return Computable.make((ctx) => this.getLastLogs(res, lines, ctx));
|
|
70
|
+
|
|
71
|
+
const r = treeEntryToResourceInfo(res, ctx);
|
|
72
|
+
const callerId = randomUUID();
|
|
73
|
+
ctx.attacheHooks(this.hooks);
|
|
74
|
+
ctx.addOnDestroy(() => this.releaseLastLogs(r.id, callerId));
|
|
75
|
+
|
|
76
|
+
const result = this.getLastLogsNoCtx(ctx.watcher, r, lines, callerId);
|
|
77
|
+
ctx.markUnstable(
|
|
78
|
+
'The logs are from stream, so we consider them unstable. Final values will be got from blobs.'
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
return result;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private getLastLogsNoCtx(
|
|
85
|
+
w: Watcher,
|
|
86
|
+
rInfo: ResourceInfo,
|
|
87
|
+
lines: number,
|
|
88
|
+
callerId: string
|
|
89
|
+
): string | undefined {
|
|
90
|
+
let logGetter = this.idToLastLines.get(rInfo.id);
|
|
91
|
+
|
|
92
|
+
if (logGetter == undefined) {
|
|
93
|
+
const newLogGetter = new LogGetter(this.clientLogs, rInfo, lines);
|
|
94
|
+
this.idToLastLines.set(rInfo.id, newLogGetter);
|
|
95
|
+
|
|
96
|
+
logGetter = newLogGetter;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
logGetter.attach(w, callerId);
|
|
100
|
+
const result = logGetter.getLog();
|
|
101
|
+
if (result.error != undefined) throw result.error;
|
|
102
|
+
|
|
103
|
+
return result.log;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Returns a last line that has patternToSearch.
|
|
107
|
+
* Notifies when a new line appeared or EOF reached. */
|
|
108
|
+
getProgressLog(
|
|
109
|
+
res: ResourceInfo | PlTreeEntry,
|
|
110
|
+
patternToSearch: string
|
|
111
|
+
): Computable<string | undefined>;
|
|
112
|
+
getProgressLog(
|
|
113
|
+
res: ResourceInfo | PlTreeEntry,
|
|
114
|
+
patternToSearch: string,
|
|
115
|
+
ctx: ComputableCtx
|
|
116
|
+
): string | undefined;
|
|
117
|
+
getProgressLog(
|
|
118
|
+
res: ResourceInfo | PlTreeEntry,
|
|
119
|
+
patternToSearch: string,
|
|
120
|
+
ctx?: ComputableCtx
|
|
121
|
+
): Computable<string | undefined> | string | undefined {
|
|
122
|
+
if (ctx == undefined)
|
|
123
|
+
return Computable.make((ctx) =>
|
|
124
|
+
this.getProgressLog(res, patternToSearch, ctx)
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
const r = treeEntryToResourceInfo(res, ctx);
|
|
128
|
+
const callerId = randomUUID();
|
|
129
|
+
ctx.attacheHooks(this.hooks);
|
|
130
|
+
ctx.addOnDestroy(() => this.releaseProgressLog(r.id, callerId));
|
|
131
|
+
|
|
132
|
+
const result = this.getProgressLogNoCtx(
|
|
133
|
+
ctx.watcher,
|
|
134
|
+
r,
|
|
135
|
+
patternToSearch,
|
|
136
|
+
callerId
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
ctx.markUnstable(
|
|
140
|
+
'The progress log is from the stream, so we consider it unstable. Final value will be got from blobs.'
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
return result;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
private getProgressLogNoCtx(
|
|
147
|
+
w: Watcher,
|
|
148
|
+
rInfo: ResourceInfo,
|
|
149
|
+
patternToSearch: string,
|
|
150
|
+
callerId: string
|
|
151
|
+
): string | undefined {
|
|
152
|
+
let logGetter = this.idToProgressLog.get(rInfo.id);
|
|
153
|
+
|
|
154
|
+
if (logGetter == undefined) {
|
|
155
|
+
const newLogGetter = new LogGetter(
|
|
156
|
+
this.clientLogs,
|
|
157
|
+
rInfo,
|
|
158
|
+
1,
|
|
159
|
+
patternToSearch
|
|
160
|
+
);
|
|
161
|
+
this.idToProgressLog.set(rInfo.id, newLogGetter);
|
|
162
|
+
|
|
163
|
+
logGetter = newLogGetter;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
logGetter.attach(w, callerId);
|
|
167
|
+
const result = logGetter.getLog();
|
|
168
|
+
if (result.error) throw result.error;
|
|
169
|
+
|
|
170
|
+
return result.log;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
getLogHandle(res: ResourceInfo | PlTreeEntry): Computable<sdk.AnyLogHandle>;
|
|
174
|
+
getLogHandle(
|
|
175
|
+
res: ResourceInfo | PlTreeEntry,
|
|
176
|
+
ctx: ComputableCtx
|
|
177
|
+
): sdk.AnyLogHandle;
|
|
178
|
+
getLogHandle(
|
|
179
|
+
res: ResourceInfo | PlTreeEntry,
|
|
180
|
+
ctx?: ComputableCtx
|
|
181
|
+
): Computable<sdk.AnyLogHandle> | sdk.AnyLogHandle {
|
|
182
|
+
if (ctx == undefined)
|
|
183
|
+
return Computable.make((ctx) => this.getLogHandle(res, ctx));
|
|
184
|
+
|
|
185
|
+
const r = treeEntryToResourceInfo(res, ctx);
|
|
186
|
+
|
|
187
|
+
const result = this.getLogHandleNoCtx(r);
|
|
188
|
+
// All logs from streams should be considered unstable,
|
|
189
|
+
// final value will be got from blobs.
|
|
190
|
+
ctx.markUnstable();
|
|
191
|
+
|
|
192
|
+
return result;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
private getLogHandleNoCtx(rInfo: ResourceInfo): sdk.AnyLogHandle {
|
|
196
|
+
return dataToHandle(true, rInfo);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async lastLines(
|
|
200
|
+
handle: sdk.AnyLogHandle,
|
|
201
|
+
lineCount: number,
|
|
202
|
+
offsetBytes?: number,
|
|
203
|
+
searchStr?: string | undefined
|
|
204
|
+
) {
|
|
205
|
+
return await this.tryWithNotFound(handle, () =>
|
|
206
|
+
this.clientLogs.lastLines(
|
|
207
|
+
handleToData(handle),
|
|
208
|
+
lineCount,
|
|
209
|
+
BigInt(offsetBytes ?? 0),
|
|
210
|
+
searchStr
|
|
211
|
+
)
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
async readText(
|
|
216
|
+
handle: sdk.AnyLogHandle,
|
|
217
|
+
lineCount: number,
|
|
218
|
+
offsetBytes?: number,
|
|
219
|
+
searchStr?: string | undefined
|
|
220
|
+
) {
|
|
221
|
+
return await this.tryWithNotFound(handle, () =>
|
|
222
|
+
this.clientLogs.readText(
|
|
223
|
+
handleToData(handle),
|
|
224
|
+
lineCount,
|
|
225
|
+
BigInt(offsetBytes ?? 0),
|
|
226
|
+
searchStr
|
|
227
|
+
)
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
private async tryWithNotFound(
|
|
232
|
+
handle: sdk.AnyLogHandle,
|
|
233
|
+
method: () => Promise<StreamingAPI_Response>
|
|
234
|
+
): Promise<sdk.StreamingApiResponse> {
|
|
235
|
+
if (!isLiveLogHandle(handle))
|
|
236
|
+
throw new Error(
|
|
237
|
+
`Not live log handle was passed to live log driver, handle: ${handle}`
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
try {
|
|
241
|
+
const resp = await method();
|
|
242
|
+
return {
|
|
243
|
+
live: true,
|
|
244
|
+
shouldUpdateHandle: false,
|
|
245
|
+
data: resp.data,
|
|
246
|
+
size: Number(resp.size),
|
|
247
|
+
newOffset: Number(resp.newOffset)
|
|
248
|
+
};
|
|
249
|
+
} catch (e: any) {
|
|
250
|
+
if (e.name == 'RpcError' && e.code == 'NOT_FOUND') {
|
|
251
|
+
return { shouldUpdateHandle: true };
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
throw e;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
private async releaseLastLogs(rId: ResourceId, callerId: string) {
|
|
259
|
+
const deleted = this.idToLastLines.get(rId)?.release(callerId);
|
|
260
|
+
if (deleted) this.idToLastLines.delete(rId);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
private async releaseProgressLog(rId: ResourceId, callerId: string) {
|
|
264
|
+
const deleted = this.idToProgressLog.get(rId)?.release(callerId);
|
|
265
|
+
if (deleted) this.idToProgressLog.delete(rId);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
async releaseAll() {}
|
|
269
|
+
|
|
270
|
+
private scheduledOnNextState: ScheduledRefresh[] = [];
|
|
271
|
+
|
|
272
|
+
private scheduleOnNextState(
|
|
273
|
+
resolve: () => void,
|
|
274
|
+
reject: (err: any) => void
|
|
275
|
+
): void {
|
|
276
|
+
this.scheduledOnNextState.push({ resolve, reject });
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/** Called from observer */
|
|
280
|
+
private startUpdating(): void {
|
|
281
|
+
this.keepRunning = true;
|
|
282
|
+
if (this.currentLoop === undefined) this.currentLoop = this.mainLoop();
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/** Called from observer */
|
|
286
|
+
private stopUpdating(): void {
|
|
287
|
+
this.keepRunning = false;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/** If true, main loop will continue polling pl state. */
|
|
291
|
+
private keepRunning = false;
|
|
292
|
+
/** Actual state of main loop. */
|
|
293
|
+
private currentLoop: Promise<void> | undefined = undefined;
|
|
294
|
+
|
|
295
|
+
private async mainLoop() {
|
|
296
|
+
while (this.keepRunning) {
|
|
297
|
+
const toNotify = this.scheduledOnNextState;
|
|
298
|
+
this.scheduledOnNextState = [];
|
|
299
|
+
|
|
300
|
+
try {
|
|
301
|
+
await asyncPool(
|
|
302
|
+
this.opts.nConcurrentGetLogs,
|
|
303
|
+
this.getAllNotDoneLogs().map(
|
|
304
|
+
(getter) => async () => await getter.update()
|
|
305
|
+
)
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
toNotify.forEach((n) => n.resolve());
|
|
309
|
+
} catch (e: any) {
|
|
310
|
+
console.error(e);
|
|
311
|
+
toNotify.forEach((n) => n.reject(e));
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (!this.keepRunning) break;
|
|
315
|
+
await scheduler.wait(this.opts.pollingInterval);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
this.currentLoop = undefined;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
private getAllNotDoneLogs(): Array<LogGetter> {
|
|
322
|
+
return Array.from(this.idToLastLines.entries())
|
|
323
|
+
.concat(Array.from(this.idToProgressLog.entries()))
|
|
324
|
+
.filter(([_, getter]) => !getter.getLog().done)
|
|
325
|
+
.map(([_, getter]) => getter);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/** A job that gets last lines from a StreamWorkdir resource. */
|
|
330
|
+
class LogGetter {
|
|
331
|
+
private logs: string | undefined;
|
|
332
|
+
private error: any | undefined = undefined;
|
|
333
|
+
private done = false;
|
|
334
|
+
|
|
335
|
+
private readonly change: ChangeSource = new ChangeSource();
|
|
336
|
+
private readonly counter: CallersCounter = new CallersCounter();
|
|
337
|
+
|
|
338
|
+
constructor(
|
|
339
|
+
private readonly clientLogs: ClientLogs,
|
|
340
|
+
private readonly rInfo: ResourceInfo,
|
|
341
|
+
private readonly lines: number,
|
|
342
|
+
private readonly patternToSearch?: string
|
|
343
|
+
) {}
|
|
344
|
+
|
|
345
|
+
getLog(): {
|
|
346
|
+
log: string | undefined;
|
|
347
|
+
error?: any | undefined;
|
|
348
|
+
done: boolean;
|
|
349
|
+
} {
|
|
350
|
+
return {
|
|
351
|
+
log: this.logs,
|
|
352
|
+
error: this.error,
|
|
353
|
+
done: this.done
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
attach(w: Watcher, callerId: string) {
|
|
358
|
+
this.change.attachWatcher(w);
|
|
359
|
+
this.counter.inc(callerId);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
release(callerId: string): boolean {
|
|
363
|
+
return this.counter.dec(callerId);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
async update() {
|
|
367
|
+
try {
|
|
368
|
+
const resp = await this.clientLogs.lastLines(
|
|
369
|
+
this.rInfo,
|
|
370
|
+
this.lines,
|
|
371
|
+
0n,
|
|
372
|
+
this.patternToSearch
|
|
373
|
+
);
|
|
374
|
+
|
|
375
|
+
const newLogs = resp.data.toString();
|
|
376
|
+
|
|
377
|
+
if (this.logs != newLogs) this.change.markChanged();
|
|
378
|
+
this.logs = newLogs;
|
|
379
|
+
|
|
380
|
+
return;
|
|
381
|
+
} catch (e: any) {
|
|
382
|
+
if (e.name == 'RpcError' && e.code == 'NOT_FOUND') {
|
|
383
|
+
// No resource
|
|
384
|
+
this.logs = '';
|
|
385
|
+
this.error = e;
|
|
386
|
+
this.done = true;
|
|
387
|
+
this.change.markChanged();
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
throw e;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
type ScheduledRefresh = {
|
|
397
|
+
resolve: () => void;
|
|
398
|
+
reject: (err: any) => void;
|
|
399
|
+
};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ConsoleLoggerAdapter,
|
|
3
|
+
HmacSha256Signer
|
|
4
|
+
} from '@milaboratories/ts-helpers';
|
|
5
|
+
import { LsDriver } from './ls';
|
|
6
|
+
import { createLsFilesClient } from '../clients/helpers';
|
|
7
|
+
import { TestHelpers } from '@milaboratories/pl-client';
|
|
8
|
+
import * as os from 'node:os';
|
|
9
|
+
|
|
10
|
+
test('should ok when get all storages from ls driver', async () => {
|
|
11
|
+
const signer = new HmacSha256Signer('abc');
|
|
12
|
+
const logger = new ConsoleLoggerAdapter();
|
|
13
|
+
await TestHelpers.withTempRoot(async (client) => {
|
|
14
|
+
const lsClient = createLsFilesClient(client, logger);
|
|
15
|
+
const driver = new LsDriver(logger, lsClient, client, signer, {
|
|
16
|
+
local: os.homedir()
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const got = await driver.getStorageList();
|
|
20
|
+
|
|
21
|
+
expect(got.length).toBeGreaterThanOrEqual(1);
|
|
22
|
+
expect(got.find((se) => se.name == 'library')?.handle).toContain('library');
|
|
23
|
+
expect(got.find((se) => se.name == 'library')?.initialFullPath).toEqual('');
|
|
24
|
+
expect(got.find((se) => se.name == 'local')?.handle).toContain('/');
|
|
25
|
+
expect(got.find((se) => se.name == 'local')?.initialFullPath).toEqual(
|
|
26
|
+
os.homedir()
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
console.log('got all storage entries: ', got);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('should ok when list files from remote storage in ls driver', async () => {
|
|
34
|
+
const signer = new HmacSha256Signer('abc');
|
|
35
|
+
const logger = new ConsoleLoggerAdapter();
|
|
36
|
+
await TestHelpers.withTempRoot(async (client) => {
|
|
37
|
+
const lsClient = createLsFilesClient(client, logger);
|
|
38
|
+
const driver = new LsDriver(logger, lsClient, client, signer, {
|
|
39
|
+
local: os.homedir()
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const storages = await driver.getStorageList();
|
|
43
|
+
const library = storages.find((se) => se.name == 'library')!.handle;
|
|
44
|
+
|
|
45
|
+
const topLevelDir = await driver.listFiles(library, '');
|
|
46
|
+
expect(topLevelDir.entries.length).toBeGreaterThan(1);
|
|
47
|
+
|
|
48
|
+
const testDir = topLevelDir.entries.find((d) =>
|
|
49
|
+
d.name.includes('ls_dir_structure')
|
|
50
|
+
);
|
|
51
|
+
expect(testDir).toBeDefined();
|
|
52
|
+
expect(testDir!.type).toEqual('dir');
|
|
53
|
+
expect(testDir!.fullPath).toEqual('/ls_dir_structure_test');
|
|
54
|
+
expect(testDir!.name).toEqual('ls_dir_structure_test');
|
|
55
|
+
|
|
56
|
+
const secondDirs = await driver.listFiles(library, testDir!.fullPath);
|
|
57
|
+
expect(secondDirs.parent).toEqual('/ls_dir_structure_test/');
|
|
58
|
+
expect(secondDirs.entries).toHaveLength(2);
|
|
59
|
+
expect(secondDirs.entries[0].type).toEqual('dir');
|
|
60
|
+
expect(secondDirs.entries[0].fullPath).toEqual(
|
|
61
|
+
'/ls_dir_structure_test/abc'
|
|
62
|
+
);
|
|
63
|
+
expect(secondDirs.entries[0].name).toEqual('abc');
|
|
64
|
+
|
|
65
|
+
const f = await driver.listFiles(library, secondDirs.entries[0].fullPath);
|
|
66
|
+
expect(f.parent).toEqual('/ls_dir_structure_test/abc/');
|
|
67
|
+
expect(f.entries).toHaveLength(1);
|
|
68
|
+
expect(f.entries[0].type).toEqual('file');
|
|
69
|
+
expect(f.entries[0].fullPath).toEqual('/ls_dir_structure_test/abc/42.txt');
|
|
70
|
+
expect(f.entries[0].name).toEqual('42.txt');
|
|
71
|
+
expect((f.entries[0] as any).handle).toContain('index://index/');
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('should ok when list files from local storage in ls driver', async () => {
|
|
76
|
+
const signer = new HmacSha256Signer('abc');
|
|
77
|
+
const logger = new ConsoleLoggerAdapter();
|
|
78
|
+
await TestHelpers.withTempRoot(async (client) => {
|
|
79
|
+
const lsClient = createLsFilesClient(client, logger);
|
|
80
|
+
const driver = new LsDriver(logger, lsClient, client, signer, {
|
|
81
|
+
local: os.homedir()
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const storages = await driver.getStorageList();
|
|
85
|
+
const local = storages.find((se) => se.name == 'local')!.handle;
|
|
86
|
+
|
|
87
|
+
const topLevelDir = await driver.listFiles(local, '');
|
|
88
|
+
expect(topLevelDir.entries.length).toBeGreaterThan(1);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isNotNullResourceId,
|
|
3
|
+
PlClient,
|
|
4
|
+
ResourceData,
|
|
5
|
+
ResourceId
|
|
6
|
+
} from '@milaboratories/pl-client';
|
|
7
|
+
import { MiLogger, Signer } from '@milaboratories/ts-helpers';
|
|
8
|
+
import * as sdk from '@milaboratories/pl-model-common';
|
|
9
|
+
import { ClientLs } from '../clients/ls_api';
|
|
10
|
+
import * as path from 'node:path';
|
|
11
|
+
import * as fs from 'node:fs/promises';
|
|
12
|
+
import {
|
|
13
|
+
createUploadHandle,
|
|
14
|
+
ListResponse,
|
|
15
|
+
toListItem,
|
|
16
|
+
toLsEntries
|
|
17
|
+
} from './helpers/ls_list_entry';
|
|
18
|
+
import { fromStorageHandle, toStorageEntry } from './helpers/ls_storage_entry';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Extends public and safe SDK's driver API with methods used internally in the middle
|
|
22
|
+
* layer and in tests.
|
|
23
|
+
*/
|
|
24
|
+
export interface InternalLsDriver extends sdk.LsDriver {
|
|
25
|
+
/**
|
|
26
|
+
* Given local path, generates well structured and signed upload handle.
|
|
27
|
+
* To be used in tests and in implementation of the native file selection UI API.
|
|
28
|
+
* */
|
|
29
|
+
getLocalFileHandle(localPath: string): Promise<sdk.ImportFileHandleUpload>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export class LsDriver implements InternalLsDriver {
|
|
33
|
+
private storageIdToResourceId?: Record<string, ResourceId>;
|
|
34
|
+
|
|
35
|
+
constructor(
|
|
36
|
+
private readonly logger: MiLogger,
|
|
37
|
+
private readonly clientLs: ClientLs,
|
|
38
|
+
private readonly client: PlClient,
|
|
39
|
+
private readonly signer: Signer,
|
|
40
|
+
private readonly localStorageToPath: Record<string, string>
|
|
41
|
+
) {}
|
|
42
|
+
|
|
43
|
+
public async getLocalFileHandle(
|
|
44
|
+
localPath: string
|
|
45
|
+
): Promise<sdk.ImportFileHandleUpload> {
|
|
46
|
+
const stat = await fs.stat(localPath, { bigint: true });
|
|
47
|
+
return createUploadHandle(
|
|
48
|
+
localPath,
|
|
49
|
+
this.signer,
|
|
50
|
+
stat.size,
|
|
51
|
+
stat.mtimeMs / 1000n // integer division
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
public async getStorageList(): Promise<sdk.StorageEntry[]> {
|
|
56
|
+
return toStorageEntry(
|
|
57
|
+
this.localStorageToPath,
|
|
58
|
+
await this.getAvailableStorageIds()
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
public async listFiles(
|
|
63
|
+
storageHandle: sdk.StorageHandle,
|
|
64
|
+
path: string
|
|
65
|
+
): Promise<sdk.ListFilesResult> {
|
|
66
|
+
const storage = fromStorageHandle(storageHandle);
|
|
67
|
+
|
|
68
|
+
let list: ListResponse;
|
|
69
|
+
|
|
70
|
+
if (storage.remote) {
|
|
71
|
+
list = await this.clientLs.list(storage, path);
|
|
72
|
+
} else {
|
|
73
|
+
list = await this.getLocalFiles(this.logger, storage.path, path);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return toLsEntries({
|
|
77
|
+
storageName: storage.name,
|
|
78
|
+
list,
|
|
79
|
+
signer: this.signer,
|
|
80
|
+
remote: storage.remote
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private async getAvailableStorageIds() {
|
|
85
|
+
if (this.storageIdToResourceId == undefined)
|
|
86
|
+
this.storageIdToResourceId = await doGetAvailableStorageIds(this.client);
|
|
87
|
+
|
|
88
|
+
return this.storageIdToResourceId;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
private async getLocalFiles(
|
|
92
|
+
logger: MiLogger,
|
|
93
|
+
_storagePath: string,
|
|
94
|
+
pathInStorage: string
|
|
95
|
+
): Promise<ListResponse> {
|
|
96
|
+
const storagePath = path.resolve(_storagePath);
|
|
97
|
+
|
|
98
|
+
const fullPath = path.isAbsolute(pathInStorage)
|
|
99
|
+
? pathInStorage
|
|
100
|
+
: path.resolve(path.join(storagePath, pathInStorage));
|
|
101
|
+
|
|
102
|
+
const files = await fs.opendir(fullPath);
|
|
103
|
+
const direntsWithStats: any[] = [];
|
|
104
|
+
for await (const dirent of files) {
|
|
105
|
+
// We cannot use no dirent.path no dirent.parentPath,
|
|
106
|
+
// since the former is deprecated
|
|
107
|
+
// and the later works differently on different versions.
|
|
108
|
+
const fullName = path.join(fullPath, dirent.name);
|
|
109
|
+
|
|
110
|
+
direntsWithStats.push({
|
|
111
|
+
directory: fullPath,
|
|
112
|
+
fullName,
|
|
113
|
+
dirent,
|
|
114
|
+
stat: await fs.stat(fullName)
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const resp: ListResponse = {
|
|
119
|
+
delimiter: path.sep,
|
|
120
|
+
items: direntsWithStats
|
|
121
|
+
.map((ds) => toListItem(logger, ds))
|
|
122
|
+
.filter((item) => item != undefined)
|
|
123
|
+
.map((item) => item!)
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
return resp;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function doGetAvailableStorageIds(
|
|
131
|
+
client: PlClient
|
|
132
|
+
): Promise<Record<string, ResourceId>> {
|
|
133
|
+
return client.withReadTx('GetAvailableStorageIds', async (tx) => {
|
|
134
|
+
const lsProviderId = await tx.getResourceByName('LSProvider');
|
|
135
|
+
const provider = await tx.getResourceData(lsProviderId, true);
|
|
136
|
+
|
|
137
|
+
return providerToStorageIds(provider);
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function providerToStorageIds(provider: ResourceData) {
|
|
142
|
+
return Object.fromEntries(
|
|
143
|
+
provider.fields
|
|
144
|
+
.filter((f) => f.type == 'Dynamic' && isNotNullResourceId(f.value))
|
|
145
|
+
.map((f) => [f.name.substring('storage/'.length), f.value as ResourceId])
|
|
146
|
+
);
|
|
147
|
+
}
|