@milaboratories/pl-drivers 1.5.4 → 1.5.6
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/drivers/download_blob.d.ts.map +1 -1
- package/dist/drivers/download_blob_task.d.ts +9 -0
- package/dist/drivers/download_blob_task.d.ts.map +1 -1
- package/dist/drivers/download_url.d.ts +7 -0
- package/dist/drivers/download_url.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +253 -220
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/drivers/download_blob.test.ts +52 -4
- package/src/drivers/download_blob.ts +7 -4
- package/src/drivers/download_blob_task.ts +31 -12
- package/src/drivers/download_url.ts +24 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@milaboratories/pl-drivers",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.6",
|
|
4
4
|
"description": "Drivers and a low-level clients for log streaming, downloading and uploading files from and to pl",
|
|
5
5
|
"types": "./dist/index.d.ts",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -28,9 +28,9 @@
|
|
|
28
28
|
"zod": "~3.23.8",
|
|
29
29
|
"@milaboratories/ts-helpers": "^1.1.3",
|
|
30
30
|
"@milaboratories/computable": "^2.3.4",
|
|
31
|
+
"@milaboratories/pl-client": "^2.7.1",
|
|
31
32
|
"@milaboratories/pl-tree": "^1.4.20",
|
|
32
|
-
"@milaboratories/pl-model-common": "^1.10.
|
|
33
|
-
"@milaboratories/pl-client": "^2.7.1"
|
|
33
|
+
"@milaboratories/pl-model-common": "^1.10.2"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"typescript": "~5.5.4",
|
|
@@ -20,7 +20,7 @@ import { OnDemandBlobResourceSnapshot } from './types';
|
|
|
20
20
|
|
|
21
21
|
const fileName = 'answer_to_the_ultimate_question.txt';
|
|
22
22
|
|
|
23
|
-
test
|
|
23
|
+
test('should download a blob and read its content', async () => {
|
|
24
24
|
await TestHelpers.withTempRoot(async (client) => {
|
|
25
25
|
const logger = new ConsoleLoggerAdapter();
|
|
26
26
|
const dir = await fsp.mkdtemp(path.join(os.tmpdir(), 'test-download-1-'));
|
|
@@ -49,7 +49,55 @@ test.skip('should download a blob and read its content', async () => {
|
|
|
49
49
|
});
|
|
50
50
|
});
|
|
51
51
|
|
|
52
|
-
test
|
|
52
|
+
test('should not redownload a blob a file already exists', async () => {
|
|
53
|
+
await TestHelpers.withTempRoot(async (client) => {
|
|
54
|
+
const logger = new ConsoleLoggerAdapter();
|
|
55
|
+
const dir = await fsp.mkdtemp(path.join(os.tmpdir(), 'test-download-1-'));
|
|
56
|
+
|
|
57
|
+
const signer = new HmacSha256Signer(HmacSha256Signer.generateSecret());
|
|
58
|
+
|
|
59
|
+
const driver = new DownloadDriver(
|
|
60
|
+
logger,
|
|
61
|
+
createDownloadClient(logger, client, []),
|
|
62
|
+
createLogsClient(client, logger),
|
|
63
|
+
dir,
|
|
64
|
+
signer,
|
|
65
|
+
{ cacheSoftSizeBytes: 700 * 1024, nConcurrentDownloads: 10 }
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
console.log('Download the first time');
|
|
69
|
+
const downloadable = await makeDownloadableBlobFromAssets(client, fileName);
|
|
70
|
+
const c = driver.getDownloadedBlob(downloadable);
|
|
71
|
+
await c.getValue();
|
|
72
|
+
await c.awaitChange();
|
|
73
|
+
const blob = await c.getValue();
|
|
74
|
+
expect(blob).toBeDefined();
|
|
75
|
+
expect(blob!.size).toBe(3);
|
|
76
|
+
expect((await driver.getContent(blob!.handle))?.toString()).toBe('42\n');
|
|
77
|
+
|
|
78
|
+
await driver.releaseAll();
|
|
79
|
+
|
|
80
|
+
const driver2 = new DownloadDriver(
|
|
81
|
+
logger,
|
|
82
|
+
createDownloadClient(logger, client, []),
|
|
83
|
+
createLogsClient(client, logger),
|
|
84
|
+
dir,
|
|
85
|
+
signer,
|
|
86
|
+
{ cacheSoftSizeBytes: 700 * 1024, nConcurrentDownloads: 10 }
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
console.log('Download the second time');
|
|
90
|
+
const c2 = driver2.getDownloadedBlob(downloadable);
|
|
91
|
+
await c2.getValue();
|
|
92
|
+
await c2.awaitChange();
|
|
93
|
+
const blob2 = await c2.getValue();
|
|
94
|
+
expect(blob2).toBeDefined();
|
|
95
|
+
expect(blob2!.size).toBe(3);
|
|
96
|
+
expect((await driver.getContent(blob2!.handle))?.toString()).toBe('42\n');
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test('should get on demand blob without downloading a blob', async () => {
|
|
53
101
|
await TestHelpers.withTempRoot(async (client) => {
|
|
54
102
|
const logger = new ConsoleLoggerAdapter();
|
|
55
103
|
const dir = await fsp.mkdtemp(path.join(os.tmpdir(), 'test-download-2-'));
|
|
@@ -74,7 +122,7 @@ test.skip('should get on demand blob without downloading a blob', async () => {
|
|
|
74
122
|
});
|
|
75
123
|
});
|
|
76
124
|
|
|
77
|
-
test
|
|
125
|
+
test('should get undefined when releasing a blob from a small cache and the blob was deleted.', async () => {
|
|
78
126
|
await TestHelpers.withTempRoot(async (client) => {
|
|
79
127
|
const logger = new ConsoleLoggerAdapter();
|
|
80
128
|
const dir = await fsp.mkdtemp(path.join(os.tmpdir(), 'test-download-3-'));
|
|
@@ -111,7 +159,7 @@ test.skip('should get undefined when releasing a blob from a small cache and the
|
|
|
111
159
|
});
|
|
112
160
|
});
|
|
113
161
|
|
|
114
|
-
test
|
|
162
|
+
test('should get the blob when releasing a blob, but a cache is big enough and it keeps a file on the local drive.', async () => {
|
|
115
163
|
await TestHelpers.withTempRoot(async (client) => {
|
|
116
164
|
const logger = new ConsoleLoggerAdapter();
|
|
117
165
|
const dir = await fsp.mkdtemp(path.join(os.tmpdir(), 'test-download-4-'));
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
ComputableStableDefined,
|
|
6
6
|
Watcher
|
|
7
7
|
} from '@milaboratories/computable';
|
|
8
|
-
import { ResourceId, ResourceType } from '@milaboratories/pl-client';
|
|
8
|
+
import { ResourceId, ResourceType, stringifyWithResourceId } from '@milaboratories/pl-client';
|
|
9
9
|
import {
|
|
10
10
|
AnyLogHandle,
|
|
11
11
|
BlobDriver,
|
|
@@ -435,8 +435,8 @@ export class DownloadDriver implements BlobDriver {
|
|
|
435
435
|
|
|
436
436
|
this.removeTask(
|
|
437
437
|
task,
|
|
438
|
-
`the task ${task.
|
|
439
|
-
`from cache along with ${toDelete.map((d) => d.
|
|
438
|
+
`the task ${stringifyWithResourceId(task.info())} was removed` +
|
|
439
|
+
`from cache along with ${stringifyWithResourceId(toDelete.map((d) => d.info()))}`
|
|
440
440
|
);
|
|
441
441
|
})
|
|
442
442
|
);
|
|
@@ -444,7 +444,10 @@ export class DownloadDriver implements BlobDriver {
|
|
|
444
444
|
// The task is still in a downloading queue.
|
|
445
445
|
const deleted = task.counter.dec(callerId);
|
|
446
446
|
if (deleted) {
|
|
447
|
-
this.removeTask(
|
|
447
|
+
this.removeTask(
|
|
448
|
+
task,
|
|
449
|
+
`the task ${stringifyWithResourceId(task.info())} was removed from cache`
|
|
450
|
+
);
|
|
448
451
|
}
|
|
449
452
|
}
|
|
450
453
|
}
|
|
@@ -34,6 +34,17 @@ export class DownloadBlobTask {
|
|
|
34
34
|
private readonly handle: LocalBlobHandle
|
|
35
35
|
) {}
|
|
36
36
|
|
|
37
|
+
/** Returns a simple object that describes this task. */
|
|
38
|
+
public info() {
|
|
39
|
+
return {
|
|
40
|
+
rInfo: this.rInfo,
|
|
41
|
+
path: this.path,
|
|
42
|
+
done: this.done,
|
|
43
|
+
size: this.size,
|
|
44
|
+
error: this.error
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
37
48
|
public attach(w: Watcher, callerId: string) {
|
|
38
49
|
this.counter.inc(callerId);
|
|
39
50
|
if (!this.done) this.change.attachWatcher(w);
|
|
@@ -41,18 +52,7 @@ export class DownloadBlobTask {
|
|
|
41
52
|
|
|
42
53
|
public async download() {
|
|
43
54
|
try {
|
|
44
|
-
await
|
|
45
|
-
const { content, size } = await this.clientDownload.downloadBlob(this.rInfo);
|
|
46
|
-
|
|
47
|
-
if (await fileExists(this.path)) {
|
|
48
|
-
await content.cancel(`the file already exists.`); // finalize body
|
|
49
|
-
} else {
|
|
50
|
-
await createPathAtomically(this.logger, this.path, async (fPath: string) => {
|
|
51
|
-
const f = Writable.toWeb(fs.createWriteStream(fPath, { flags: 'wx' }));
|
|
52
|
-
await content.pipeTo(f);
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
|
|
55
|
+
const size = await this.ensureDownloaded();
|
|
56
56
|
this.setDone(size);
|
|
57
57
|
this.change.markChanged();
|
|
58
58
|
} catch (e: any) {
|
|
@@ -67,6 +67,25 @@ export class DownloadBlobTask {
|
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
private async ensureDownloaded() {
|
|
71
|
+
await ensureDirExists(path.dirname(this.path));
|
|
72
|
+
|
|
73
|
+
if (await fileExists(this.path)) {
|
|
74
|
+
this.logger.info(`a blob was already downloaded: ${this.path}`);
|
|
75
|
+
const stat = await fsp.stat(this.path);
|
|
76
|
+
return stat.size;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const { content, size } = await this.clientDownload.downloadBlob(this.rInfo);
|
|
80
|
+
|
|
81
|
+
await createPathAtomically(this.logger, this.path, async (fPath: string) => {
|
|
82
|
+
const f = Writable.toWeb(fs.createWriteStream(fPath, { flags: 'wx' }));
|
|
83
|
+
await content.pipeTo(f);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
return size;
|
|
87
|
+
}
|
|
88
|
+
|
|
70
89
|
public abort(reason: string) {
|
|
71
90
|
this.signalCtl.abort(new DownloadAborted(reason));
|
|
72
91
|
}
|
|
@@ -17,6 +17,8 @@ import * as tar from 'tar-fs';
|
|
|
17
17
|
import { Dispatcher } from 'undici';
|
|
18
18
|
import { NetworkError400, RemoteFileDownloader } from '../helpers/download';
|
|
19
19
|
import { FilesCache } from './helpers/files_cache';
|
|
20
|
+
import { stringifyWithResourceId } from '@milaboratories/pl-client';
|
|
21
|
+
import { DownloadBlobTask } from './download_blob_task';
|
|
20
22
|
|
|
21
23
|
export interface DownloadUrlSyncReader {
|
|
22
24
|
/** Returns a Computable that (when the time will come)
|
|
@@ -128,21 +130,25 @@ export class DownloadUrlDriver implements DownloadUrlSyncReader {
|
|
|
128
130
|
const toDelete = this.cache.removeFile(task.path, callerId);
|
|
129
131
|
|
|
130
132
|
await Promise.all(
|
|
131
|
-
toDelete.map(async (task) => {
|
|
133
|
+
toDelete.map(async (task: DownloadByUrlTask) => {
|
|
132
134
|
await rmRFDir(task.path);
|
|
133
135
|
this.cache.removeCache(task);
|
|
134
136
|
|
|
135
137
|
this.removeTask(
|
|
136
138
|
task,
|
|
137
|
-
`the task ${
|
|
138
|
-
`from cache along with ${
|
|
139
|
+
`the task ${stringifyWithResourceId(task.info())} was removed` +
|
|
140
|
+
`from cache along with ${stringifyWithResourceId(toDelete.map((t) => t.info()))}`
|
|
139
141
|
);
|
|
140
142
|
})
|
|
141
143
|
);
|
|
142
144
|
} else {
|
|
143
145
|
// The task is still in a downloading queue.
|
|
144
146
|
const deleted = task.counter.dec(callerId);
|
|
145
|
-
if (deleted)
|
|
147
|
+
if (deleted)
|
|
148
|
+
this.removeTask(
|
|
149
|
+
task,
|
|
150
|
+
`the task ${stringifyWithResourceId(task.info())} was removed from cache`
|
|
151
|
+
);
|
|
146
152
|
}
|
|
147
153
|
}
|
|
148
154
|
|
|
@@ -155,7 +161,10 @@ export class DownloadUrlDriver implements DownloadUrlSyncReader {
|
|
|
155
161
|
await rmRFDir(task.path);
|
|
156
162
|
this.cache.removeCache(task);
|
|
157
163
|
|
|
158
|
-
this.removeTask(
|
|
164
|
+
this.removeTask(
|
|
165
|
+
task,
|
|
166
|
+
`the task ${stringifyWithResourceId(task.info())} was released when the driver was closed`
|
|
167
|
+
);
|
|
159
168
|
})
|
|
160
169
|
);
|
|
161
170
|
}
|
|
@@ -195,6 +204,16 @@ class DownloadByUrlTask {
|
|
|
195
204
|
readonly url: URL
|
|
196
205
|
) {}
|
|
197
206
|
|
|
207
|
+
public info() {
|
|
208
|
+
return {
|
|
209
|
+
url: this.url.toString(),
|
|
210
|
+
path: this.path,
|
|
211
|
+
done: this.done,
|
|
212
|
+
size: this.size,
|
|
213
|
+
error: this.error
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
198
217
|
attach(w: Watcher, callerId: string) {
|
|
199
218
|
this.counter.inc(callerId);
|
|
200
219
|
if (!this.done) this.change.attachWatcher(w);
|