@milaboratories/pl-drivers 1.3.25 → 1.4.0
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/dist/clients/constructors.d.ts +14 -0
- package/dist/clients/constructors.d.ts.map +1 -0
- package/dist/clients/download.d.ts +19 -14
- package/dist/clients/download.d.ts.map +1 -1
- package/dist/clients/helpers.d.ts +4 -13
- package/dist/clients/helpers.d.ts.map +1 -1
- package/dist/clients/upload.d.ts +15 -13
- package/dist/clients/upload.d.ts.map +1 -1
- package/dist/drivers/{download_and_logs_blob.d.ts → download_blob.d.ts} +13 -40
- package/dist/drivers/download_blob.d.ts.map +1 -0
- package/dist/drivers/download_blob_task.d.ts +34 -0
- package/dist/drivers/download_blob_task.d.ts.map +1 -0
- package/dist/drivers/download_url.d.ts +11 -9
- package/dist/drivers/download_url.d.ts.map +1 -1
- package/dist/drivers/helpers/download_local_handle.d.ts +9 -0
- package/dist/drivers/helpers/download_local_handle.d.ts.map +1 -0
- package/dist/drivers/helpers/download_remote_handle.d.ts +8 -0
- package/dist/drivers/helpers/download_remote_handle.d.ts.map +1 -0
- package/dist/drivers/helpers/files_cache.d.ts +2 -1
- package/dist/drivers/helpers/files_cache.d.ts.map +1 -1
- package/dist/drivers/helpers/helpers.d.ts +2 -24
- package/dist/drivers/helpers/helpers.d.ts.map +1 -1
- package/dist/drivers/helpers/logs_handle.d.ts +13 -0
- package/dist/drivers/helpers/logs_handle.d.ts.map +1 -0
- package/dist/drivers/helpers/ls_remote_import_handle.d.ts +8 -0
- package/dist/drivers/helpers/ls_remote_import_handle.d.ts.map +1 -0
- package/dist/drivers/logs.d.ts +1 -5
- package/dist/drivers/logs.d.ts.map +1 -1
- package/dist/drivers/logs_stream.d.ts.map +1 -1
- package/dist/drivers/ls.d.ts.map +1 -1
- package/dist/drivers/types.d.ts +47 -4
- package/dist/drivers/types.d.ts.map +1 -1
- package/dist/drivers/upload.d.ts +2 -28
- package/dist/drivers/upload.d.ts.map +1 -1
- package/dist/drivers/upload_task.d.ts +41 -0
- package/dist/drivers/upload_task.d.ts.map +1 -0
- package/dist/helpers/download.d.ts +2 -2
- package/dist/helpers/download.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1537 -1526
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -5
- package/src/clients/constructors.ts +54 -0
- package/src/clients/download.test.ts +9 -6
- package/src/clients/download.ts +74 -64
- package/src/clients/helpers.ts +2 -53
- package/src/clients/upload.ts +136 -102
- package/src/drivers/download_blob.test.ts +12 -11
- package/src/drivers/{download_and_logs_blob.ts → download_blob.ts} +154 -290
- package/src/drivers/download_blob_task.ts +126 -0
- package/src/drivers/download_url.test.ts +1 -1
- package/src/drivers/download_url.ts +44 -37
- package/src/drivers/helpers/download_local_handle.ts +29 -0
- package/src/drivers/helpers/download_remote_handle.ts +40 -0
- package/src/drivers/helpers/files_cache.test.ts +7 -6
- package/src/drivers/helpers/files_cache.ts +6 -5
- package/src/drivers/helpers/helpers.ts +6 -100
- package/src/drivers/helpers/logs_handle.ts +52 -0
- package/src/drivers/helpers/ls_remote_import_handle.ts +43 -0
- package/src/drivers/logs.test.ts +14 -14
- package/src/drivers/logs.ts +3 -43
- package/src/drivers/logs_stream.ts +32 -6
- package/src/drivers/ls.test.ts +1 -2
- package/src/drivers/ls.ts +26 -28
- package/src/drivers/types.ts +48 -0
- package/src/drivers/upload.test.ts +8 -18
- package/src/drivers/upload.ts +38 -271
- package/src/drivers/upload_task.ts +251 -0
- package/src/helpers/download.ts +18 -15
- package/src/index.ts +2 -2
- package/dist/drivers/download_and_logs_blob.d.ts.map +0 -1
- package/dist/drivers/helpers/ls_list_entry.d.ts +0 -44
- package/dist/drivers/helpers/ls_list_entry.d.ts.map +0 -1
- package/src/drivers/helpers/ls_list_entry.test.ts +0 -55
- package/src/drivers/helpers/ls_list_entry.ts +0 -147
|
@@ -1,21 +1,22 @@
|
|
|
1
|
-
import
|
|
2
|
-
import * as os from 'node:os';
|
|
3
|
-
import * as path from 'node:path';
|
|
4
|
-
import { ConsoleLoggerAdapter, HmacSha256Signer } from '@milaboratories/ts-helpers';
|
|
1
|
+
import { expect, test } from '@jest/globals';
|
|
5
2
|
import {
|
|
3
|
+
FieldId,
|
|
4
|
+
FieldRef,
|
|
5
|
+
jsonToData,
|
|
6
6
|
PlClient,
|
|
7
7
|
PlTransaction,
|
|
8
|
-
TestHelpers,
|
|
9
|
-
jsonToData,
|
|
10
|
-
FieldRef,
|
|
11
8
|
poll,
|
|
12
9
|
PollTxAccessor,
|
|
13
|
-
|
|
14
|
-
FieldId
|
|
10
|
+
TestHelpers
|
|
15
11
|
} from '@milaboratories/pl-client';
|
|
12
|
+
import { ConsoleLoggerAdapter, HmacSha256Signer } from '@milaboratories/ts-helpers';
|
|
13
|
+
import * as fsp from 'node:fs/promises';
|
|
14
|
+
import * as os from 'node:os';
|
|
15
|
+
import * as path from 'node:path';
|
|
16
16
|
import { scheduler } from 'node:timers/promises';
|
|
17
|
-
import { createDownloadClient, createLogsClient } from '../clients/
|
|
18
|
-
import { DownloadDriver
|
|
17
|
+
import { createDownloadClient, createLogsClient } from '../clients/constructors';
|
|
18
|
+
import { DownloadDriver } from './download_blob';
|
|
19
|
+
import { OnDemandBlobResourceSnapshot } from './types';
|
|
19
20
|
|
|
20
21
|
const fileName = 'answer_to_the_ultimate_question.txt';
|
|
21
22
|
|
|
@@ -5,40 +5,7 @@ import {
|
|
|
5
5
|
ComputableStableDefined,
|
|
6
6
|
Watcher
|
|
7
7
|
} from '@milaboratories/computable';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
CallersCounter,
|
|
11
|
-
MiLogger,
|
|
12
|
-
TaskProcessor,
|
|
13
|
-
Signer,
|
|
14
|
-
ValueOrError
|
|
15
|
-
} from '@milaboratories/ts-helpers';
|
|
16
|
-
import * as fsp from 'node:fs/promises';
|
|
17
|
-
import * as fs from 'fs';
|
|
18
|
-
import * as path from 'node:path';
|
|
19
|
-
import { Writable } from 'node:stream';
|
|
20
|
-
import { ClientDownload, UnknownStorageError, WrongLocalFileUrl } from '../clients/download';
|
|
21
|
-
import { ClientLogs } from '../clients/logs';
|
|
22
|
-
import * as helper from './helpers/helpers';
|
|
23
|
-
import * as readline from 'node:readline/promises';
|
|
24
|
-
import Denque from 'denque';
|
|
25
|
-
import * as os from 'node:os';
|
|
26
|
-
import { FilesCache } from './helpers/files_cache';
|
|
27
|
-
import { randomUUID } from 'node:crypto';
|
|
28
|
-
import { buffer } from 'node:stream/consumers';
|
|
29
|
-
import { Readable } from 'node:stream';
|
|
30
|
-
import {
|
|
31
|
-
InferSnapshot,
|
|
32
|
-
ResourceInfo,
|
|
33
|
-
PlTreeEntry,
|
|
34
|
-
ResourceWithMetadata,
|
|
35
|
-
rsSchema,
|
|
36
|
-
makeResourceSnapshot,
|
|
37
|
-
treeEntryToResourceWithMetadata,
|
|
38
|
-
ResourceSnapshot,
|
|
39
|
-
treeEntryToResourceInfo,
|
|
40
|
-
isPlTreeEntry
|
|
41
|
-
} from '@milaboratories/pl-tree';
|
|
8
|
+
import { ResourceId, ResourceType } from '@milaboratories/pl-client';
|
|
42
9
|
import {
|
|
43
10
|
AnyLogHandle,
|
|
44
11
|
BlobDriver,
|
|
@@ -49,20 +16,41 @@ import {
|
|
|
49
16
|
RemoteBlobHandleAndSize,
|
|
50
17
|
StreamingApiResponse
|
|
51
18
|
} from '@milaboratories/pl-model-common';
|
|
52
|
-
import {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
|
|
19
|
+
import {
|
|
20
|
+
isPlTreeEntry,
|
|
21
|
+
makeResourceSnapshot,
|
|
22
|
+
PlTreeEntry,
|
|
23
|
+
ResourceInfo,
|
|
24
|
+
ResourceSnapshot,
|
|
25
|
+
treeEntryToResourceInfo
|
|
26
|
+
} from '@milaboratories/pl-tree';
|
|
27
|
+
import { CallersCounter, MiLogger, Signer, TaskProcessor } from '@milaboratories/ts-helpers';
|
|
28
|
+
import Denque from 'denque';
|
|
29
|
+
import * as fs from 'fs';
|
|
30
|
+
import { randomUUID } from 'node:crypto';
|
|
31
|
+
import * as fsp from 'node:fs/promises';
|
|
32
|
+
import * as os from 'node:os';
|
|
33
|
+
import * as path from 'node:path';
|
|
34
|
+
import * as readline from 'node:readline/promises';
|
|
35
|
+
import { Readable, Writable } from 'node:stream';
|
|
36
|
+
import { buffer } from 'node:stream/consumers';
|
|
37
|
+
import { ClientDownload } from '../clients/download';
|
|
38
|
+
import { ClientLogs } from '../clients/logs';
|
|
39
|
+
import { DownloadBlobTask, nonRecoverableError } from './download_blob_task';
|
|
40
|
+
import { FilesCache } from './helpers/files_cache';
|
|
41
|
+
import {
|
|
42
|
+
isLocalBlobHandle,
|
|
43
|
+
newLocalHandle,
|
|
44
|
+
parseLocalHandle
|
|
45
|
+
} from './helpers/download_local_handle';
|
|
46
|
+
import { getSize, OnDemandBlobResourceSnapshot } from './types';
|
|
47
|
+
import {
|
|
48
|
+
isRemoteBlobHandle,
|
|
49
|
+
newRemoteHandle,
|
|
50
|
+
parseRemoteHandle
|
|
51
|
+
} from './helpers/download_remote_handle';
|
|
52
|
+
import { getResourceInfoFromLogHandle, newLogHandle } from './helpers/logs_handle';
|
|
53
|
+
import { Updater, WrongResourceTypeError } from './helpers/helpers';
|
|
66
54
|
|
|
67
55
|
export type DownloadDriverOps = {
|
|
68
56
|
/**
|
|
@@ -82,11 +70,11 @@ export type DownloadDriverOps = {
|
|
|
82
70
|
* and notifies every watcher when a file were downloaded. */
|
|
83
71
|
export class DownloadDriver implements BlobDriver {
|
|
84
72
|
/** Represents a Resource Id to the path of a blob as a map. */
|
|
85
|
-
private idToDownload: Map<ResourceId,
|
|
73
|
+
private idToDownload: Map<ResourceId, DownloadBlobTask> = new Map();
|
|
86
74
|
|
|
87
75
|
/** Writes and removes files to a hard drive and holds a counter for every
|
|
88
76
|
* file that should be kept. */
|
|
89
|
-
private cache: FilesCache<
|
|
77
|
+
private cache: FilesCache<DownloadBlobTask>;
|
|
90
78
|
|
|
91
79
|
/** Downloads files and writes them to the local dir. */
|
|
92
80
|
private downloadQueue: TaskProcessor;
|
|
@@ -113,14 +101,14 @@ export class DownloadDriver implements BlobDriver {
|
|
|
113
101
|
}
|
|
114
102
|
|
|
115
103
|
/** Gets a blob by its resource id or downloads a blob and sets it in a cache.*/
|
|
116
|
-
getDownloadedBlob(
|
|
104
|
+
public getDownloadedBlob(
|
|
117
105
|
res: ResourceInfo | PlTreeEntry,
|
|
118
106
|
ctx: ComputableCtx
|
|
119
107
|
): LocalBlobHandleAndSize | undefined;
|
|
120
|
-
getDownloadedBlob(
|
|
108
|
+
public getDownloadedBlob(
|
|
121
109
|
res: ResourceInfo | PlTreeEntry
|
|
122
110
|
): ComputableStableDefined<LocalBlobHandleAndSize>;
|
|
123
|
-
getDownloadedBlob(
|
|
111
|
+
public getDownloadedBlob(
|
|
124
112
|
res: ResourceInfo | PlTreeEntry,
|
|
125
113
|
ctx?: ComputableCtx
|
|
126
114
|
): Computable<LocalBlobHandleAndSize | undefined> | LocalBlobHandleAndSize | undefined {
|
|
@@ -137,117 +125,133 @@ export class DownloadDriver implements BlobDriver {
|
|
|
137
125
|
return result;
|
|
138
126
|
}
|
|
139
127
|
|
|
140
|
-
getOnDemandBlob(
|
|
141
|
-
res: OnDemandBlobResourceSnapshot | PlTreeEntry
|
|
142
|
-
): Computable<RemoteBlobHandleAndSize>;
|
|
143
|
-
getOnDemandBlob(
|
|
144
|
-
res: OnDemandBlobResourceSnapshot | PlTreeEntry,
|
|
145
|
-
ctx: ComputableCtx
|
|
146
|
-
): RemoteBlobHandleAndSize;
|
|
147
|
-
getOnDemandBlob(
|
|
148
|
-
res: OnDemandBlobResourceSnapshot | PlTreeEntry,
|
|
149
|
-
ctx?: ComputableCtx
|
|
150
|
-
): ComputableStableDefined<RemoteBlobHandleAndSize> | RemoteBlobHandleAndSize | undefined {
|
|
151
|
-
if (ctx === undefined) return Computable.make((ctx) => this.getOnDemandBlob(res, ctx));
|
|
152
|
-
|
|
153
|
-
const rInfo: OnDemandBlobResourceSnapshot = isPlTreeEntry(res)
|
|
154
|
-
? makeResourceSnapshot(res, OnDemandBlobResourceSnapshot, ctx)
|
|
155
|
-
: res;
|
|
156
|
-
|
|
157
|
-
const callerId = randomUUID();
|
|
158
|
-
ctx.addOnDestroy(() => this.releaseOnDemandBlob(rInfo.id, callerId));
|
|
159
|
-
|
|
160
|
-
const result = this.getOnDemandBlobNoCtx(ctx.watcher, rInfo, callerId);
|
|
161
|
-
|
|
162
|
-
return result;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
public getLocalPath(handle: LocalBlobHandle): string {
|
|
166
|
-
return localHandleToPath(handle, this.signer);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
public async getContent(handle: LocalBlobHandle | RemoteBlobHandle): Promise<Uint8Array> {
|
|
170
|
-
if (isLocalBlobHandle(handle)) return await read(this.getLocalPath(handle));
|
|
171
|
-
|
|
172
|
-
if (!isRemoteBlobHandle(handle)) throw new Error('Malformed remote handle');
|
|
173
|
-
|
|
174
|
-
const result = remoteHandleToData(handle, this.signer);
|
|
175
|
-
const { content } = await this.clientDownload.downloadBlob(result);
|
|
176
|
-
|
|
177
|
-
return await buffer(content);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
128
|
private getDownloadedBlobNoCtx(
|
|
181
129
|
w: Watcher,
|
|
182
130
|
rInfo: ResourceSnapshot,
|
|
183
131
|
callerId: string
|
|
184
132
|
): LocalBlobHandleAndSize | undefined {
|
|
133
|
+
validateDownloadableResourceType('getDownloadedBlob', rInfo.type);
|
|
134
|
+
|
|
185
135
|
let task = this.idToDownload.get(rInfo.id);
|
|
186
136
|
|
|
187
137
|
if (task === undefined) {
|
|
188
138
|
// schedule the blob downloading
|
|
189
|
-
const newTask = this.setNewDownloadTask(
|
|
139
|
+
const newTask = this.setNewDownloadTask(rInfo);
|
|
190
140
|
this.downloadQueue.push({
|
|
191
141
|
fn: () => this.downloadBlob(newTask, callerId),
|
|
192
|
-
recoverableErrorPredicate: (
|
|
142
|
+
recoverableErrorPredicate: (e) => !nonRecoverableError(e)
|
|
193
143
|
});
|
|
194
144
|
task = newTask;
|
|
195
145
|
}
|
|
196
146
|
|
|
197
147
|
task.attach(w, callerId);
|
|
198
148
|
const result = task.getBlob();
|
|
199
|
-
if (result
|
|
200
|
-
if (result.ok) return result.value;
|
|
201
|
-
throw result.error;
|
|
149
|
+
if (!result.done) return undefined;
|
|
150
|
+
if (result.result.ok) return result.result.value;
|
|
151
|
+
throw result.result.error;
|
|
202
152
|
}
|
|
203
153
|
|
|
204
|
-
private setNewDownloadTask(
|
|
154
|
+
private setNewDownloadTask(rInfo: ResourceSnapshot) {
|
|
205
155
|
const fPath = this.getFilePath(rInfo.id);
|
|
206
|
-
const result = new
|
|
156
|
+
const result = new DownloadBlobTask(
|
|
157
|
+
this.logger,
|
|
207
158
|
this.clientDownload,
|
|
208
159
|
rInfo,
|
|
209
160
|
fPath,
|
|
210
|
-
|
|
161
|
+
newLocalHandle(fPath, this.signer)
|
|
211
162
|
);
|
|
212
163
|
this.idToDownload.set(rInfo.id, result);
|
|
213
164
|
|
|
214
165
|
return result;
|
|
215
166
|
}
|
|
216
167
|
|
|
217
|
-
private async downloadBlob(task:
|
|
168
|
+
private async downloadBlob(task: DownloadBlobTask, callerId: string) {
|
|
218
169
|
await task.download();
|
|
219
|
-
|
|
170
|
+
const blob = task.getBlob();
|
|
171
|
+
if (blob.done && blob.result.ok) {
|
|
172
|
+
this.cache.addCache(task, callerId);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Gets on demand blob. */
|
|
177
|
+
public getOnDemandBlob(
|
|
178
|
+
res: OnDemandBlobResourceSnapshot | PlTreeEntry
|
|
179
|
+
): Computable<RemoteBlobHandleAndSize>;
|
|
180
|
+
public getOnDemandBlob(
|
|
181
|
+
res: OnDemandBlobResourceSnapshot | PlTreeEntry,
|
|
182
|
+
ctx: ComputableCtx
|
|
183
|
+
): RemoteBlobHandleAndSize;
|
|
184
|
+
public getOnDemandBlob(
|
|
185
|
+
res: OnDemandBlobResourceSnapshot | PlTreeEntry,
|
|
186
|
+
ctx?: ComputableCtx
|
|
187
|
+
): ComputableStableDefined<RemoteBlobHandleAndSize> | RemoteBlobHandleAndSize | undefined {
|
|
188
|
+
if (ctx === undefined) return Computable.make((ctx) => this.getOnDemandBlob(res, ctx));
|
|
189
|
+
|
|
190
|
+
const rInfo: OnDemandBlobResourceSnapshot = isPlTreeEntry(res)
|
|
191
|
+
? makeResourceSnapshot(res, OnDemandBlobResourceSnapshot, ctx)
|
|
192
|
+
: res;
|
|
193
|
+
|
|
194
|
+
const callerId = randomUUID();
|
|
195
|
+
ctx.addOnDestroy(() => this.releaseOnDemandBlob(rInfo.id, callerId));
|
|
196
|
+
|
|
197
|
+
// note that the watcher is not needed,
|
|
198
|
+
// the handler never changes.
|
|
199
|
+
const result = this.getOnDemandBlobNoCtx(rInfo, callerId);
|
|
200
|
+
|
|
201
|
+
return result;
|
|
220
202
|
}
|
|
221
203
|
|
|
222
204
|
private getOnDemandBlobNoCtx(
|
|
223
|
-
w: Watcher,
|
|
224
205
|
info: OnDemandBlobResourceSnapshot,
|
|
225
206
|
callerId: string
|
|
226
207
|
): RemoteBlobHandleAndSize {
|
|
208
|
+
validateDownloadableResourceType('getOnDemandBlob', info.type);
|
|
209
|
+
|
|
227
210
|
let blob = this.idToOnDemand.get(info.id);
|
|
228
211
|
|
|
229
212
|
if (blob === undefined) {
|
|
230
|
-
blob = new OnDemandBlobHolder(
|
|
231
|
-
info.kv['ctl/file/blobInfo'].sizeBytes,
|
|
232
|
-
dataToRemoteHandle(info, this.signer)
|
|
233
|
-
);
|
|
213
|
+
blob = new OnDemandBlobHolder(getSize(info), newRemoteHandle(info, this.signer));
|
|
234
214
|
this.idToOnDemand.set(info.id, blob);
|
|
235
215
|
}
|
|
236
216
|
|
|
237
|
-
blob.attach(
|
|
217
|
+
blob.attach(callerId);
|
|
238
218
|
|
|
239
219
|
return blob.getHandle();
|
|
240
220
|
}
|
|
241
221
|
|
|
222
|
+
/** Gets a path from a handle. */
|
|
223
|
+
public getLocalPath(handle: LocalBlobHandle): string {
|
|
224
|
+
const { path } = parseLocalHandle(handle, this.signer);
|
|
225
|
+
return path;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/** Gets a content of a blob by a handle. */
|
|
229
|
+
public async getContent(handle: LocalBlobHandle | RemoteBlobHandle): Promise<Uint8Array> {
|
|
230
|
+
if (isLocalBlobHandle(handle)) {
|
|
231
|
+
return await read(this.getLocalPath(handle));
|
|
232
|
+
}
|
|
233
|
+
if (isRemoteBlobHandle(handle)) {
|
|
234
|
+
const result = parseRemoteHandle(handle, this.signer);
|
|
235
|
+
const { content } = await this.clientDownload.downloadBlob(result);
|
|
236
|
+
|
|
237
|
+
return await buffer(content);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
throw new Error('Malformed remote handle');
|
|
241
|
+
}
|
|
242
|
+
|
|
242
243
|
/** Returns all logs and schedules a job that reads remain logs.
|
|
243
244
|
* Notifies when a new portion of the log appeared. */
|
|
244
|
-
getLastLogs(
|
|
245
|
-
|
|
245
|
+
public getLastLogs(
|
|
246
|
+
res: ResourceInfo | PlTreeEntry,
|
|
247
|
+
lines: number
|
|
248
|
+
): Computable<string | undefined>;
|
|
249
|
+
public getLastLogs(
|
|
246
250
|
res: ResourceInfo | PlTreeEntry,
|
|
247
251
|
lines: number,
|
|
248
252
|
ctx: ComputableCtx
|
|
249
253
|
): Computable<string | undefined>;
|
|
250
|
-
getLastLogs(
|
|
254
|
+
public getLastLogs(
|
|
251
255
|
res: ResourceInfo | PlTreeEntry,
|
|
252
256
|
lines: number,
|
|
253
257
|
ctx?: ComputableCtx
|
|
@@ -271,10 +275,11 @@ export class DownloadDriver implements BlobDriver {
|
|
|
271
275
|
lines: number,
|
|
272
276
|
callerId: string
|
|
273
277
|
): string | undefined {
|
|
278
|
+
validateDownloadableResourceType('getLastLogs', rInfo.type);
|
|
274
279
|
const blob = this.getDownloadedBlobNoCtx(w, rInfo, callerId);
|
|
275
280
|
if (blob == undefined) return undefined;
|
|
276
281
|
|
|
277
|
-
const path =
|
|
282
|
+
const { path } = parseLocalHandle(blob.handle, this.signer);
|
|
278
283
|
|
|
279
284
|
let logGetter = this.idToLastLines.get(rInfo.id);
|
|
280
285
|
|
|
@@ -292,16 +297,16 @@ export class DownloadDriver implements BlobDriver {
|
|
|
292
297
|
|
|
293
298
|
/** Returns a last line that has patternToSearch.
|
|
294
299
|
* Notifies when a new line appeared or EOF reached. */
|
|
295
|
-
getProgressLog(
|
|
300
|
+
public getProgressLog(
|
|
296
301
|
res: ResourceInfo | PlTreeEntry,
|
|
297
302
|
patternToSearch: string
|
|
298
303
|
): Computable<string | undefined>;
|
|
299
|
-
getProgressLog(
|
|
304
|
+
public getProgressLog(
|
|
300
305
|
res: ResourceInfo | PlTreeEntry,
|
|
301
306
|
patternToSearch: string,
|
|
302
307
|
ctx: ComputableCtx
|
|
303
308
|
): string | undefined;
|
|
304
|
-
getProgressLog(
|
|
309
|
+
public getProgressLog(
|
|
305
310
|
res: ResourceInfo | PlTreeEntry,
|
|
306
311
|
patternToSearch: string,
|
|
307
312
|
ctx?: ComputableCtx
|
|
@@ -331,9 +336,11 @@ export class DownloadDriver implements BlobDriver {
|
|
|
331
336
|
patternToSearch: string,
|
|
332
337
|
callerId: string
|
|
333
338
|
): string | undefined {
|
|
339
|
+
validateDownloadableResourceType('getProgressLog', rInfo.type);
|
|
340
|
+
|
|
334
341
|
const blob = this.getDownloadedBlobNoCtx(w, rInfo, callerId);
|
|
335
342
|
if (blob == undefined) return undefined;
|
|
336
|
-
const path =
|
|
343
|
+
const { path } = parseLocalHandle(blob.handle, this.signer);
|
|
337
344
|
|
|
338
345
|
let logGetter = this.idToProgressLog.get(rInfo.id);
|
|
339
346
|
|
|
@@ -352,9 +359,9 @@ export class DownloadDriver implements BlobDriver {
|
|
|
352
359
|
|
|
353
360
|
/** Returns an Id of a smart object, that can read logs directly from
|
|
354
361
|
* the platform. */
|
|
355
|
-
getLogHandle(res: ResourceInfo | PlTreeEntry): Computable<AnyLogHandle>;
|
|
356
|
-
getLogHandle(res: ResourceInfo | PlTreeEntry, ctx: ComputableCtx): AnyLogHandle;
|
|
357
|
-
getLogHandle(
|
|
362
|
+
public getLogHandle(res: ResourceInfo | PlTreeEntry): Computable<AnyLogHandle>;
|
|
363
|
+
public getLogHandle(res: ResourceInfo | PlTreeEntry, ctx: ComputableCtx): AnyLogHandle;
|
|
364
|
+
public getLogHandle(
|
|
358
365
|
res: ResourceInfo | PlTreeEntry,
|
|
359
366
|
ctx?: ComputableCtx
|
|
360
367
|
): Computable<AnyLogHandle> | AnyLogHandle {
|
|
@@ -366,17 +373,18 @@ export class DownloadDriver implements BlobDriver {
|
|
|
366
373
|
}
|
|
367
374
|
|
|
368
375
|
private getLogHandleNoCtx(rInfo: ResourceSnapshot): AnyLogHandle {
|
|
369
|
-
|
|
376
|
+
validateDownloadableResourceType('getLogHandle', rInfo.type);
|
|
377
|
+
return newLogHandle(false, rInfo);
|
|
370
378
|
}
|
|
371
379
|
|
|
372
|
-
async lastLines(
|
|
380
|
+
public async lastLines(
|
|
373
381
|
handle: ReadyLogHandle,
|
|
374
382
|
lineCount: number,
|
|
375
383
|
offsetBytes?: number, // if 0n, then start from the end.
|
|
376
384
|
searchStr?: string
|
|
377
385
|
): Promise<StreamingApiResponse> {
|
|
378
386
|
const resp = await this.clientLogs.lastLines(
|
|
379
|
-
|
|
387
|
+
getResourceInfoFromLogHandle(handle),
|
|
380
388
|
lineCount,
|
|
381
389
|
BigInt(offsetBytes ?? 0),
|
|
382
390
|
searchStr
|
|
@@ -391,14 +399,14 @@ export class DownloadDriver implements BlobDriver {
|
|
|
391
399
|
};
|
|
392
400
|
}
|
|
393
401
|
|
|
394
|
-
async readText(
|
|
402
|
+
public async readText(
|
|
395
403
|
handle: ReadyLogHandle,
|
|
396
404
|
lineCount: number,
|
|
397
405
|
offsetBytes?: number,
|
|
398
406
|
searchStr?: string
|
|
399
407
|
): Promise<StreamingApiResponse> {
|
|
400
408
|
const resp = await this.clientLogs.readText(
|
|
401
|
-
|
|
409
|
+
getResourceInfoFromLogHandle(handle),
|
|
402
410
|
lineCount,
|
|
403
411
|
BigInt(offsetBytes ?? 0),
|
|
404
412
|
searchStr
|
|
@@ -435,11 +443,13 @@ export class DownloadDriver implements BlobDriver {
|
|
|
435
443
|
} else {
|
|
436
444
|
// The task is still in a downloading queue.
|
|
437
445
|
const deleted = task.counter.dec(callerId);
|
|
438
|
-
if (deleted)
|
|
446
|
+
if (deleted) {
|
|
447
|
+
this.removeTask(task, `the task ${task.path} was removed from cache`);
|
|
448
|
+
}
|
|
439
449
|
}
|
|
440
450
|
}
|
|
441
451
|
|
|
442
|
-
private removeTask(task:
|
|
452
|
+
private removeTask(task: DownloadBlobTask, reason: string) {
|
|
443
453
|
task.abort(reason);
|
|
444
454
|
task.change.markChanged();
|
|
445
455
|
this.idToDownload.delete(task.rInfo.id);
|
|
@@ -463,12 +473,12 @@ export class DownloadDriver implements BlobDriver {
|
|
|
463
473
|
}
|
|
464
474
|
|
|
465
475
|
private getFilePath(rId: ResourceId): string {
|
|
466
|
-
return path.resolve(
|
|
476
|
+
return path.resolve(this.saveDir, String(BigInt(rId)));
|
|
467
477
|
}
|
|
468
478
|
}
|
|
469
479
|
|
|
480
|
+
/** Keeps a counter to the on demand handle. */
|
|
470
481
|
class OnDemandBlobHolder {
|
|
471
|
-
private readonly change = new ChangeSource();
|
|
472
482
|
private readonly counter = new CallersCounter();
|
|
473
483
|
|
|
474
484
|
constructor(
|
|
@@ -476,22 +486,21 @@ class OnDemandBlobHolder {
|
|
|
476
486
|
private readonly handle: RemoteBlobHandle
|
|
477
487
|
) {}
|
|
478
488
|
|
|
479
|
-
getHandle(): RemoteBlobHandleAndSize {
|
|
489
|
+
public getHandle(): RemoteBlobHandleAndSize {
|
|
480
490
|
return { handle: this.handle, size: this.size };
|
|
481
491
|
}
|
|
482
492
|
|
|
483
|
-
attach(
|
|
493
|
+
public attach(callerId: string) {
|
|
484
494
|
this.counter.inc(callerId);
|
|
485
|
-
this.change.attachWatcher(w);
|
|
486
495
|
}
|
|
487
496
|
|
|
488
|
-
release(callerId: string): boolean {
|
|
497
|
+
public release(callerId: string): boolean {
|
|
489
498
|
return this.counter.dec(callerId);
|
|
490
499
|
}
|
|
491
500
|
}
|
|
492
501
|
|
|
493
502
|
class LastLinesGetter {
|
|
494
|
-
private updater:
|
|
503
|
+
private updater: Updater;
|
|
495
504
|
private log: string | undefined;
|
|
496
505
|
private readonly change: ChangeSource = new ChangeSource();
|
|
497
506
|
private error: any | undefined = undefined;
|
|
@@ -501,7 +510,7 @@ class LastLinesGetter {
|
|
|
501
510
|
private readonly lines: number,
|
|
502
511
|
private readonly patternToSearch?: string
|
|
503
512
|
) {
|
|
504
|
-
this.updater = new
|
|
513
|
+
this.updater = new Updater(async () => this.update());
|
|
505
514
|
}
|
|
506
515
|
|
|
507
516
|
getOrSchedule(w: Watcher): {
|
|
@@ -538,22 +547,9 @@ class LastLinesGetter {
|
|
|
538
547
|
}
|
|
539
548
|
}
|
|
540
549
|
|
|
541
|
-
async function fileOrDirExists(path: string): Promise<boolean> {
|
|
542
|
-
try {
|
|
543
|
-
await fsp.access(path);
|
|
544
|
-
return true;
|
|
545
|
-
} catch {
|
|
546
|
-
return false;
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
async function read(path: string): Promise<Uint8Array> {
|
|
551
|
-
return await buffer(Readable.toWeb(fs.createReadStream(path)));
|
|
552
|
-
}
|
|
553
|
-
|
|
554
550
|
/** Gets last lines from a file by reading the file from the top and keeping
|
|
555
551
|
* last N lines in a window queue. */
|
|
556
|
-
function getLastLines(fPath:
|
|
552
|
+
function getLastLines(fPath: string, nLines: number, patternToSearch?: string): Promise<string> {
|
|
557
553
|
const inStream = fs.createReadStream(fPath);
|
|
558
554
|
const outStream = new Writable();
|
|
559
555
|
|
|
@@ -579,148 +575,16 @@ function getLastLines(fPath: PathLike, nLines: number, patternToSearch?: string)
|
|
|
579
575
|
});
|
|
580
576
|
}
|
|
581
577
|
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
readonly change = new ChangeSource();
|
|
585
|
-
readonly signalCtl = new AbortController();
|
|
586
|
-
error: any | undefined;
|
|
587
|
-
done = false;
|
|
588
|
-
sizeBytes = 0;
|
|
589
|
-
|
|
590
|
-
constructor(
|
|
591
|
-
readonly clientDownload: ClientDownload,
|
|
592
|
-
readonly rInfo: ResourceSnapshot,
|
|
593
|
-
readonly path: string,
|
|
594
|
-
readonly handle: LocalBlobHandle
|
|
595
|
-
) {}
|
|
596
|
-
|
|
597
|
-
attach(w: Watcher, callerId: string) {
|
|
598
|
-
this.counter.inc(callerId);
|
|
599
|
-
if (!this.done) this.change.attachWatcher(w);
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
async download() {
|
|
603
|
-
try {
|
|
604
|
-
// TODO: move size bytes inside fileExists check like in download_url.
|
|
605
|
-
const { content, size } = await this.clientDownload.downloadBlob(this.rInfo);
|
|
606
|
-
|
|
607
|
-
if (!(await fileOrDirExists(path.dirname(this.path))))
|
|
608
|
-
await fsp.mkdir(path.dirname(this.path), { recursive: true });
|
|
609
|
-
|
|
610
|
-
// check in case we already have a file by this resource id
|
|
611
|
-
// in the directory. It can happen when we forgot to call removeAll
|
|
612
|
-
// in the previous launch.
|
|
613
|
-
if (await fileOrDirExists(this.path)) {
|
|
614
|
-
await content.cancel(`the file already existed`); // we don't need the blob
|
|
615
|
-
} else {
|
|
616
|
-
const fileToWrite = Writable.toWeb(fs.createWriteStream(this.path));
|
|
617
|
-
await content.pipeTo(fileToWrite);
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
this.setDone(size);
|
|
621
|
-
} catch (e: any) {
|
|
622
|
-
if (
|
|
623
|
-
e instanceof DownloadAborted ||
|
|
624
|
-
e instanceof NetworkError400 ||
|
|
625
|
-
e instanceof UnknownStorageError ||
|
|
626
|
-
e instanceof WrongLocalFileUrl ||
|
|
627
|
-
e.code == 'ENOENT' // file that we downloads from was moved or deleted.
|
|
628
|
-
) {
|
|
629
|
-
this.setError(e);
|
|
630
|
-
// Just in case we were half-way extracting an archive.
|
|
631
|
-
await fsp.rm(this.path);
|
|
632
|
-
return;
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
throw e;
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
getBlob(): ValueOrError<LocalBlobHandleAndSize> | undefined {
|
|
640
|
-
if (this.done)
|
|
641
|
-
return {
|
|
642
|
-
ok: true,
|
|
643
|
-
value: {
|
|
644
|
-
handle: this.handle,
|
|
645
|
-
size: this.sizeBytes
|
|
646
|
-
}
|
|
647
|
-
};
|
|
648
|
-
|
|
649
|
-
if (this.error)
|
|
650
|
-
return {
|
|
651
|
-
ok: false,
|
|
652
|
-
error: this.error
|
|
653
|
-
};
|
|
654
|
-
|
|
655
|
-
return undefined;
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
private setDone(sizeBytes: number) {
|
|
659
|
-
this.done = true;
|
|
660
|
-
this.sizeBytes = sizeBytes;
|
|
661
|
-
this.change.markChanged();
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
abort(reason: string) {
|
|
665
|
-
this.signalCtl.abort(new DownloadAborted(reason));
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
private setError(e: any) {
|
|
669
|
-
this.error = e;
|
|
670
|
-
this.change.markChanged();
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
type PathLike = string;
|
|
675
|
-
|
|
676
|
-
class DownloadAborted extends Error {}
|
|
677
|
-
|
|
678
|
-
// https://regex101.com/r/kfnBVX/1
|
|
679
|
-
const localHandleRegex = /^blob\+local:\/\/download\/(?<path>.*)#(?<signature>.*)$/;
|
|
680
|
-
|
|
681
|
-
function isLocalBlobHandle(handle: string): handle is LocalBlobHandle {
|
|
682
|
-
return Boolean(handle.match(localHandleRegex));
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
function localHandleToPath(handle: LocalBlobHandle, signer: Signer): string {
|
|
686
|
-
const parsed = handle.match(localHandleRegex);
|
|
687
|
-
|
|
688
|
-
if (parsed === null) throw new Error(`Local handle is malformed: ${handle}, matches: ${parsed}`);
|
|
689
|
-
|
|
690
|
-
const { path, signature } = parsed.groups!;
|
|
691
|
-
|
|
692
|
-
signer.verify(path, signature, `Signature verification failed for: ${handle}`);
|
|
693
|
-
|
|
694
|
-
return path;
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
function dataToLocalHandle(path: string, signer: Signer): LocalBlobHandle {
|
|
698
|
-
return `blob+local://download/${path}#${signer.sign(path)}` as LocalBlobHandle;
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
// https://regex101.com/r/rvbPZt/1
|
|
702
|
-
const remoteHandleRegex =
|
|
703
|
-
/^blob\+remote:\/\/download\/(?<content>(?<resourceType>.*)\/(?<resourceVersion>.*)\/(?<resourceId>.*))#(?<signature>.*)$/;
|
|
704
|
-
|
|
705
|
-
function isRemoteBlobHandle(handle: string): handle is RemoteBlobHandle {
|
|
706
|
-
return Boolean(handle.match(remoteHandleRegex));
|
|
578
|
+
async function read(path: string): Promise<Uint8Array> {
|
|
579
|
+
return await buffer(Readable.toWeb(fs.createReadStream(path)));
|
|
707
580
|
}
|
|
708
581
|
|
|
709
|
-
function
|
|
710
|
-
|
|
711
|
-
|
|
582
|
+
function validateDownloadableResourceType(methodName: string, rType: ResourceType) {
|
|
583
|
+
if (!rType.name.startsWith('Blob/')) {
|
|
584
|
+
let message = `${methodName}: wrong resource type: ${rType.name}, expected: a resource of type that starts with 'Blob/'.`;
|
|
585
|
+
if (rType.name == 'Blob')
|
|
586
|
+
message += ` If it's called from workflow, should a file be exported with 'file.exportFile' function?`;
|
|
712
587
|
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
signer.verify(content, signature, `Signature verification failed for ${handle}`);
|
|
716
|
-
|
|
717
|
-
return {
|
|
718
|
-
id: bigintToResourceId(BigInt(resourceId)),
|
|
719
|
-
type: { name: resourceType, version: resourceVersion }
|
|
720
|
-
};
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
function dataToRemoteHandle(rInfo: OnDemandBlobResourceSnapshot, signer: Signer): RemoteBlobHandle {
|
|
724
|
-
const content = `${rInfo.type.name}/${rInfo.type.version}/${BigInt(rInfo.id)}`;
|
|
725
|
-
return `blob+remote://download/${content}#${signer.sign(content)}` as RemoteBlobHandle;
|
|
588
|
+
throw new WrongResourceTypeError(message);
|
|
589
|
+
}
|
|
726
590
|
}
|