@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.
Files changed (78) hide show
  1. package/dist/clients/constructors.d.ts +14 -0
  2. package/dist/clients/constructors.d.ts.map +1 -0
  3. package/dist/clients/download.d.ts +19 -14
  4. package/dist/clients/download.d.ts.map +1 -1
  5. package/dist/clients/helpers.d.ts +4 -13
  6. package/dist/clients/helpers.d.ts.map +1 -1
  7. package/dist/clients/upload.d.ts +15 -13
  8. package/dist/clients/upload.d.ts.map +1 -1
  9. package/dist/drivers/{download_and_logs_blob.d.ts → download_blob.d.ts} +13 -40
  10. package/dist/drivers/download_blob.d.ts.map +1 -0
  11. package/dist/drivers/download_blob_task.d.ts +34 -0
  12. package/dist/drivers/download_blob_task.d.ts.map +1 -0
  13. package/dist/drivers/download_url.d.ts +11 -9
  14. package/dist/drivers/download_url.d.ts.map +1 -1
  15. package/dist/drivers/helpers/download_local_handle.d.ts +9 -0
  16. package/dist/drivers/helpers/download_local_handle.d.ts.map +1 -0
  17. package/dist/drivers/helpers/download_remote_handle.d.ts +8 -0
  18. package/dist/drivers/helpers/download_remote_handle.d.ts.map +1 -0
  19. package/dist/drivers/helpers/files_cache.d.ts +2 -1
  20. package/dist/drivers/helpers/files_cache.d.ts.map +1 -1
  21. package/dist/drivers/helpers/helpers.d.ts +2 -24
  22. package/dist/drivers/helpers/helpers.d.ts.map +1 -1
  23. package/dist/drivers/helpers/logs_handle.d.ts +13 -0
  24. package/dist/drivers/helpers/logs_handle.d.ts.map +1 -0
  25. package/dist/drivers/helpers/ls_remote_import_handle.d.ts +8 -0
  26. package/dist/drivers/helpers/ls_remote_import_handle.d.ts.map +1 -0
  27. package/dist/drivers/logs.d.ts +1 -5
  28. package/dist/drivers/logs.d.ts.map +1 -1
  29. package/dist/drivers/logs_stream.d.ts.map +1 -1
  30. package/dist/drivers/ls.d.ts.map +1 -1
  31. package/dist/drivers/types.d.ts +47 -4
  32. package/dist/drivers/types.d.ts.map +1 -1
  33. package/dist/drivers/upload.d.ts +2 -28
  34. package/dist/drivers/upload.d.ts.map +1 -1
  35. package/dist/drivers/upload_task.d.ts +41 -0
  36. package/dist/drivers/upload_task.d.ts.map +1 -0
  37. package/dist/helpers/download.d.ts +2 -2
  38. package/dist/helpers/download.d.ts.map +1 -1
  39. package/dist/index.d.ts +2 -2
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +2 -2
  42. package/dist/index.js.map +1 -1
  43. package/dist/index.mjs +1537 -1526
  44. package/dist/index.mjs.map +1 -1
  45. package/package.json +5 -5
  46. package/src/clients/constructors.ts +54 -0
  47. package/src/clients/download.test.ts +9 -6
  48. package/src/clients/download.ts +74 -64
  49. package/src/clients/helpers.ts +2 -53
  50. package/src/clients/upload.ts +136 -102
  51. package/src/drivers/download_blob.test.ts +12 -11
  52. package/src/drivers/{download_and_logs_blob.ts → download_blob.ts} +154 -290
  53. package/src/drivers/download_blob_task.ts +126 -0
  54. package/src/drivers/download_url.test.ts +1 -1
  55. package/src/drivers/download_url.ts +44 -37
  56. package/src/drivers/helpers/download_local_handle.ts +29 -0
  57. package/src/drivers/helpers/download_remote_handle.ts +40 -0
  58. package/src/drivers/helpers/files_cache.test.ts +7 -6
  59. package/src/drivers/helpers/files_cache.ts +6 -5
  60. package/src/drivers/helpers/helpers.ts +6 -100
  61. package/src/drivers/helpers/logs_handle.ts +52 -0
  62. package/src/drivers/helpers/ls_remote_import_handle.ts +43 -0
  63. package/src/drivers/logs.test.ts +14 -14
  64. package/src/drivers/logs.ts +3 -43
  65. package/src/drivers/logs_stream.ts +32 -6
  66. package/src/drivers/ls.test.ts +1 -2
  67. package/src/drivers/ls.ts +26 -28
  68. package/src/drivers/types.ts +48 -0
  69. package/src/drivers/upload.test.ts +8 -18
  70. package/src/drivers/upload.ts +38 -271
  71. package/src/drivers/upload_task.ts +251 -0
  72. package/src/helpers/download.ts +18 -15
  73. package/src/index.ts +2 -2
  74. package/dist/drivers/download_and_logs_blob.d.ts.map +0 -1
  75. package/dist/drivers/helpers/ls_list_entry.d.ts +0 -44
  76. package/dist/drivers/helpers/ls_list_entry.d.ts.map +0 -1
  77. package/src/drivers/helpers/ls_list_entry.test.ts +0 -55
  78. package/src/drivers/helpers/ls_list_entry.ts +0 -147
@@ -1,21 +1,22 @@
1
- import * as fsp from 'node:fs/promises';
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
- BasicResourceData,
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/helpers';
18
- import { DownloadDriver, OnDemandBlobResourceSnapshot } from './download_and_logs_blob';
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 { bigintToResourceId, ResourceId } from '@milaboratories/pl-client';
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 { dataToHandle, handleToData, isReadyLogHandle } from './logs';
53
- import { z } from 'zod';
54
- import { NetworkError400 } from '../helpers/download';
55
-
56
- /** ResourceSnapshot that can be passed to OnDemandBlob */
57
- export const OnDemandBlobResourceSnapshot = rsSchema({
58
- kv: {
59
- 'ctl/file/blobInfo': z.object({
60
- sizeBytes: z.coerce.number()
61
- })
62
- }
63
- });
64
-
65
- export type OnDemandBlobResourceSnapshot = InferSnapshot<typeof OnDemandBlobResourceSnapshot>;
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, Download> = new Map();
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<Download>;
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(w, rInfo, callerId);
139
+ const newTask = this.setNewDownloadTask(rInfo);
190
140
  this.downloadQueue.push({
191
141
  fn: () => this.downloadBlob(newTask, callerId),
192
- recoverableErrorPredicate: (_) => true
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 === undefined) return undefined;
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(w: Watcher, rInfo: ResourceSnapshot, callerId: string) {
154
+ private setNewDownloadTask(rInfo: ResourceSnapshot) {
205
155
  const fPath = this.getFilePath(rInfo.id);
206
- const result = new Download(
156
+ const result = new DownloadBlobTask(
157
+ this.logger,
207
158
  this.clientDownload,
208
159
  rInfo,
209
160
  fPath,
210
- dataToLocalHandle(fPath, this.signer)
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: Download, callerId: string) {
168
+ private async downloadBlob(task: DownloadBlobTask, callerId: string) {
218
169
  await task.download();
219
- if (task.getBlob()?.ok) this.cache.addCache(task, callerId);
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(w, callerId);
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(res: ResourceInfo | PlTreeEntry, lines: number): Computable<string | undefined>;
245
- getLastLogs(
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 = localHandleToPath(blob.handle, this.signer);
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 = localHandleToPath(blob.handle, this.signer);
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
- return dataToHandle(false, rInfo);
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
- handleToData(handle),
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
- handleToData(handle),
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) this.removeTask(task, `the task ${task.path} was removed from cache`);
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: Download, reason: string) {
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(path.join(this.saveDir, String(BigInt(rId))));
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(w: Watcher, callerId: string) {
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: helper.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 helper.Updater(async () => this.update());
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: PathLike, nLines: number, patternToSearch?: string): Promise<string> {
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
- export class Download {
583
- readonly counter = new CallersCounter();
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 remoteHandleToData(handle: RemoteBlobHandle, signer: Signer): ResourceInfo {
710
- const parsed = handle.match(remoteHandleRegex);
711
- if (parsed === null) throw new Error(`Remote handle is malformed: ${handle}, matches: ${parsed}`);
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
- const { content, resourceType, resourceVersion, resourceId, signature } = parsed.groups!;
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
  }