@milaboratories/pl-drivers 1.2.35 → 1.3.1

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 (38) hide show
  1. package/dist/clients/download.d.ts +6 -2
  2. package/dist/clients/download.d.ts.map +1 -1
  3. package/dist/clients/helpers.d.ts +2 -2
  4. package/dist/clients/helpers.d.ts.map +1 -1
  5. package/dist/drivers/helpers/ls_list_entry.d.ts +8 -13
  6. package/dist/drivers/helpers/ls_list_entry.d.ts.map +1 -1
  7. package/dist/drivers/helpers/ls_storage_entry.d.ts +6 -8
  8. package/dist/drivers/helpers/ls_storage_entry.d.ts.map +1 -1
  9. package/dist/drivers/ls.d.ts +40 -12
  10. package/dist/drivers/ls.d.ts.map +1 -1
  11. package/dist/drivers/types.d.ts +80 -0
  12. package/dist/drivers/types.d.ts.map +1 -0
  13. package/dist/drivers/upload.d.ts +18 -26
  14. package/dist/drivers/upload.d.ts.map +1 -1
  15. package/dist/helpers/validate.d.ts +2 -0
  16. package/dist/helpers/validate.d.ts.map +1 -0
  17. package/dist/index.d.ts +2 -0
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +1 -1
  20. package/dist/index.js.map +1 -1
  21. package/dist/index.mjs +820 -792
  22. package/dist/index.mjs.map +1 -1
  23. package/package.json +5 -5
  24. package/src/clients/download.test.ts +17 -15
  25. package/src/clients/download.ts +34 -21
  26. package/src/clients/helpers.ts +13 -43
  27. package/src/drivers/download_blob.test.ts +4 -4
  28. package/src/drivers/helpers/ls_list_entry.test.ts +5 -7
  29. package/src/drivers/helpers/ls_list_entry.ts +31 -36
  30. package/src/drivers/helpers/ls_storage_entry.ts +18 -62
  31. package/src/drivers/logs.test.ts +3 -3
  32. package/src/drivers/ls.test.ts +112 -30
  33. package/src/drivers/ls.ts +233 -83
  34. package/src/drivers/types.ts +41 -0
  35. package/src/drivers/upload.test.ts +43 -82
  36. package/src/drivers/upload.ts +59 -111
  37. package/src/helpers/validate.ts +6 -0
  38. package/src/index.ts +3 -0
@@ -1,20 +1,29 @@
1
- import {
2
- ConsoleLoggerAdapter,
3
- HmacSha256Signer
4
- } from '@milaboratories/ts-helpers';
5
- import { LsDriver } from './ls';
1
+ import { ConsoleLoggerAdapter, HmacSha256Signer } from '@milaboratories/ts-helpers';
2
+ import { DefaultVirtualLocalStorages, LsDriver } from './ls';
6
3
  import { createLsFilesClient } from '../clients/helpers';
7
4
  import { TestHelpers } from '@milaboratories/pl-client';
8
5
  import * as os from 'node:os';
6
+ import * as path from 'node:path';
7
+ import { test, expect } from '@jest/globals';
8
+ import { isImportFileHandleIndex, isImportFileHandleUpload } from '@milaboratories/pl-model-common';
9
+
10
+ const assetsPath = path.resolve('../../../assets');
9
11
 
10
12
  test('should ok when get all storages from ls driver', async () => {
11
13
  const signer = new HmacSha256Signer('abc');
12
14
  const logger = new ConsoleLoggerAdapter();
13
15
  await TestHelpers.withTempRoot(async (client) => {
14
16
  const lsClient = createLsFilesClient(client, logger);
15
- const driver = new LsDriver(logger, lsClient, client, signer, {
16
- local: os.homedir()
17
- });
17
+ const driver = await LsDriver.init(
18
+ logger,
19
+ client,
20
+ signer,
21
+ DefaultVirtualLocalStorages(),
22
+ [],
23
+ () => {
24
+ throw Error();
25
+ }
26
+ );
18
27
 
19
28
  const got = await driver.getStorageList();
20
29
 
@@ -22,9 +31,7 @@ test('should ok when get all storages from ls driver', async () => {
22
31
  expect(got.find((se) => se.name == 'library')?.handle).toContain('library');
23
32
  expect(got.find((se) => se.name == 'library')?.initialFullPath).toEqual('');
24
33
  expect(got.find((se) => se.name == 'local')?.handle).toContain('/');
25
- expect(got.find((se) => se.name == 'local')?.initialFullPath).toEqual(
26
- os.homedir()
27
- );
34
+ expect(got.find((se) => se.name == 'local')?.initialFullPath).toEqual(os.homedir());
28
35
 
29
36
  console.log('got all storage entries: ', got);
30
37
  });
@@ -34,10 +41,16 @@ test('should ok when list files from remote storage in ls driver', async () => {
34
41
  const signer = new HmacSha256Signer('abc');
35
42
  const logger = new ConsoleLoggerAdapter();
36
43
  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
- });
44
+ const driver = await LsDriver.init(
45
+ logger,
46
+ client,
47
+ signer,
48
+ DefaultVirtualLocalStorages(),
49
+ [],
50
+ () => {
51
+ throw Error();
52
+ }
53
+ );
41
54
 
42
55
  const storages = await driver.getStorageList();
43
56
  const library = storages.find((se) => se.name == 'library')!.handle;
@@ -45,25 +58,19 @@ test('should ok when list files from remote storage in ls driver', async () => {
45
58
  const topLevelDir = await driver.listFiles(library, '');
46
59
  expect(topLevelDir.entries.length).toBeGreaterThan(1);
47
60
 
48
- const testDir = topLevelDir.entries.find((d) =>
49
- d.name.includes('ls_dir_structure')
50
- );
61
+ const testDir = topLevelDir.entries.find((d) => d.name.includes('ls_dir_structure'));
51
62
  expect(testDir).toBeDefined();
52
63
  expect(testDir!.type).toEqual('dir');
53
64
  expect(testDir!.fullPath).toEqual('/ls_dir_structure_test');
54
65
  expect(testDir!.name).toEqual('ls_dir_structure_test');
55
66
 
56
67
  const secondDirs = await driver.listFiles(library, testDir!.fullPath);
57
- expect(secondDirs.parent).toEqual('/ls_dir_structure_test/');
58
68
  expect(secondDirs.entries).toHaveLength(2);
59
69
  expect(secondDirs.entries[0].type).toEqual('dir');
60
- expect(secondDirs.entries[0].fullPath).toEqual(
61
- '/ls_dir_structure_test/abc'
62
- );
70
+ expect(secondDirs.entries[0].fullPath).toEqual('/ls_dir_structure_test/abc');
63
71
  expect(secondDirs.entries[0].name).toEqual('abc');
64
72
 
65
73
  const f = await driver.listFiles(library, secondDirs.entries[0].fullPath);
66
- expect(f.parent).toEqual('/ls_dir_structure_test/abc/');
67
74
  expect(f.entries).toHaveLength(1);
68
75
  expect(f.entries[0].type).toEqual('file');
69
76
  expect(f.entries[0].fullPath).toEqual('/ls_dir_structure_test/abc/42.txt');
@@ -76,15 +83,90 @@ test('should ok when list files from local storage in ls driver', async () => {
76
83
  const signer = new HmacSha256Signer('abc');
77
84
  const logger = new ConsoleLoggerAdapter();
78
85
  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
- });
86
+ const driver = await LsDriver.init(
87
+ logger,
88
+ client,
89
+ signer,
90
+ DefaultVirtualLocalStorages(),
91
+ [],
92
+ () => {
93
+ throw Error();
94
+ }
95
+ );
83
96
 
84
97
  const storages = await driver.getStorageList();
85
- const local = storages.find((se) => se.name == 'local')!.handle;
98
+ const local = storages.find((se) => se.name == 'local')!;
86
99
 
87
- const topLevelDir = await driver.listFiles(local, '');
88
- expect(topLevelDir.entries.length).toBeGreaterThan(1);
100
+ const defaultDir = await driver.listFiles(local.handle, local.initialFullPath);
101
+ expect(defaultDir.entries.length).toBeGreaterThan(1);
102
+ });
103
+ });
104
+
105
+ test('should ok when list files from local storage in ls driver and correctly apply local projections', async () => {
106
+ const signer = new HmacSha256Signer('abc');
107
+ const logger = new ConsoleLoggerAdapter();
108
+ await TestHelpers.withTempRoot(async (client) => {
109
+ let dialogRet = '';
110
+ const driver = await LsDriver.init(
111
+ logger,
112
+ client,
113
+ signer,
114
+ DefaultVirtualLocalStorages(),
115
+ [{ storageId: 'test_storage', localPath: path.join(assetsPath, 'ls_dir_structure_test') }],
116
+ async () => [dialogRet]
117
+ );
118
+
119
+ const storages = await driver.getStorageList();
120
+ const local = storages.find((se) => se.name == 'local')!;
121
+
122
+ {
123
+ dialogRet = path.join(assetsPath, 'ls_dir_structure_test', 'abc', '42.txt');
124
+ const result = await driver.showOpenSingleFileDialog();
125
+ expect(result.file).toBeDefined();
126
+
127
+ expect(isImportFileHandleIndex(result.file!)).toStrictEqual(true);
128
+ const size = await driver.getLocalFileSize(result.file!);
129
+ expect(size).toStrictEqual(3);
130
+ const content = await driver.getLocalFileContent(result.file!);
131
+ expect(Buffer.from(content).toString()).toStrictEqual('42\n');
132
+ }
133
+
134
+ {
135
+ dialogRet = path.join(assetsPath, 'answer_to_the_ultimate_question.txt');
136
+ const result = await driver.showOpenSingleFileDialog();
137
+ expect(result.file).toBeDefined();
138
+
139
+ expect(isImportFileHandleUpload(result.file!)).toStrictEqual(true);
140
+ const size = await driver.getLocalFileSize(result.file!);
141
+ expect(size).toStrictEqual(3);
142
+ const content = await driver.getLocalFileContent(result.file!);
143
+ expect(Buffer.from(content).toString()).toStrictEqual('42\n');
144
+ }
145
+ });
146
+ });
147
+
148
+ test('should ok when get file using local dialog, and read its content', async () => {
149
+ const signer = new HmacSha256Signer('abc');
150
+ const logger = new ConsoleLoggerAdapter();
151
+ await TestHelpers.withTempRoot(async (client) => {
152
+ const driver = await LsDriver.init(
153
+ logger,
154
+ client,
155
+ signer,
156
+ DefaultVirtualLocalStorages(),
157
+ [],
158
+ async () => [path.join(assetsPath, 'answer_to_the_ultimate_question.txt')]
159
+ );
160
+
161
+ const result = await driver.showOpenSingleFileDialog();
162
+ expect(result.file).toBeDefined();
163
+
164
+ const size = await driver.getLocalFileSize(result.file!);
165
+ expect(size).toStrictEqual(3);
166
+ const content = await driver.getLocalFileContent(result.file!);
167
+ expect(Buffer.from(content).toString()).toStrictEqual('42\n');
168
+
169
+ const multiResult = await driver.showOpenMultipleFilesDialog();
170
+ expect(multiResult.files![0]).toStrictEqual(result.file);
89
171
  });
90
172
  });
package/src/drivers/ls.ts CHANGED
@@ -1,21 +1,29 @@
1
- import {
2
- isNotNullResourceId,
3
- PlClient,
4
- ResourceData,
5
- ResourceId
6
- } from '@milaboratories/pl-client';
1
+ import { isNotNullResourceId, PlClient, ResourceData, ResourceId } from '@milaboratories/pl-client';
7
2
  import { MiLogger, Signer } from '@milaboratories/ts-helpers';
8
3
  import * as sdk from '@milaboratories/pl-model-common';
4
+ import {
5
+ isImportFileHandleIndex,
6
+ LocalImportFileHandle,
7
+ LsEntry,
8
+ OpenDialogOps,
9
+ OpenMultipleFilesResponse,
10
+ OpenSingleFileResponse,
11
+ TableRange
12
+ } from '@milaboratories/pl-model-common';
9
13
  import { ClientLs } from '../clients/ls_api';
10
14
  import * as path from 'node:path';
11
- import * as fs from 'node:fs/promises';
15
+ import * as fsp from 'node:fs/promises';
12
16
  import {
13
- createUploadHandle,
14
- ListResponse,
15
- toListItem,
16
- toLsEntries
17
+ createIndexImportHandle,
18
+ createUploadImportHandle,
19
+ parseIndexHandle,
20
+ parseUploadHandle
17
21
  } from './helpers/ls_list_entry';
18
- import { fromStorageHandle, toStorageEntry } from './helpers/ls_storage_entry';
22
+ import { createLocalStorageHandle, createRemoteStorageHandle, parseStorageHandle } from './helpers/ls_storage_entry';
23
+ import { LocalStorageProjection } from './types';
24
+ import * as os from 'node:os';
25
+ import { createLsFilesClient } from '../clients/helpers';
26
+ import { validateAbsolute } from '../helpers/validate';
19
27
 
20
28
  /**
21
29
  * Extends public and safe SDK's driver API with methods used internally in the middle
@@ -23,113 +31,255 @@ import { fromStorageHandle, toStorageEntry } from './helpers/ls_storage_entry';
23
31
  */
24
32
  export interface InternalLsDriver extends sdk.LsDriver {
25
33
  /**
26
- * Given local path, generates well structured and signed upload handle.
34
+ * Given local path, generates well-structured and signed upload handle.
27
35
  * To be used in tests and in implementation of the native file selection UI API.
28
36
  * */
29
- getLocalFileHandle(localPath: string): Promise<sdk.ImportFileHandleUpload>;
37
+ getLocalFileHandle(localPath: string): Promise<sdk.LocalImportFileHandle>;
30
38
  }
31
39
 
32
- export class LsDriver implements InternalLsDriver {
33
- private storageIdToResourceId?: Record<string, ResourceId>;
40
+ export type OpenFileDialogCallback = (
41
+ multipleFiles: boolean,
42
+ ops?: OpenDialogOps
43
+ ) => Promise<undefined | string[]>;
44
+
45
+ /** Allows to add parts of local FS as virtual storages, presenting homogeneous API to UI */
46
+ export type VirtualLocalStorageSpec = {
47
+ /** Virtual storage ID, must not intersect with other storage ids */
48
+ readonly name: string;
49
+
50
+ /** Local path to "chroot" the API in */
51
+ readonly root: string;
52
+
53
+ /** Used as hint to UI controls to, set as initial path during browsing */
54
+ readonly initialPath: string;
55
+ };
34
56
 
35
- constructor(
57
+ export function DefaultVirtualLocalStorages(): VirtualLocalStorageSpec[] {
58
+ const home = os.homedir();
59
+ return path.sep === '/'
60
+ ? [{ name: 'local', root: '/', initialPath: home }]
61
+ : [
62
+ {
63
+ name: 'local',
64
+ root: path.parse(home).root, // disk where home directory resides
65
+ initialPath: home
66
+ }
67
+ ];
68
+ }
69
+
70
+ export class LsDriver implements InternalLsDriver {
71
+ private constructor(
36
72
  private readonly logger: MiLogger,
37
- private readonly clientLs: ClientLs,
38
- private readonly client: PlClient,
73
+ private readonly lsClient: ClientLs,
74
+ /** Pl storage id, to resource id. The resource id can be used to make LS GRPC calls to. */
75
+ private readonly storageIdToResourceId: Record<string, ResourceId>,
39
76
  private readonly signer: Signer,
40
- private readonly localStorageToPath: Record<string, string>
77
+ /** Virtual storages by name */
78
+ private readonly virtualStoragesMap: Map<string, VirtualLocalStorageSpec>,
79
+ /** Local projections by storageId */
80
+ private readonly localProjectionsMap: Map<string, LocalStorageProjection>,
81
+ private readonly openFileDialogCallback: OpenFileDialogCallback
41
82
  ) {}
42
83
 
84
+ public async getLocalFileContent(
85
+ file: LocalImportFileHandle,
86
+ range?: TableRange
87
+ ): Promise<Uint8Array> {
88
+ const localPath = await this.tryResolveLocalFileHandle(file);
89
+ if (range) throw new Error('Range request not yet supported.');
90
+ return await fsp.readFile(localPath);
91
+ }
92
+
93
+ public async getLocalFileSize(file: LocalImportFileHandle): Promise<number> {
94
+ const localPath = await this.tryResolveLocalFileHandle(file);
95
+ const stat = await fsp.stat(localPath);
96
+ return stat.size;
97
+ }
98
+
99
+ public async showOpenMultipleFilesDialog(
100
+ ops?: OpenDialogOps
101
+ ): Promise<OpenMultipleFilesResponse> {
102
+ const result = await this.openFileDialogCallback(true, ops);
103
+ if (result === undefined) return {};
104
+ return {
105
+ files: await Promise.all(result.map((localPath) => this.getLocalFileHandle(localPath)))
106
+ };
107
+ }
108
+
109
+ public async showOpenSingleFileDialog(ops?: OpenDialogOps): Promise<OpenSingleFileResponse> {
110
+ const result = await this.openFileDialogCallback(false, ops);
111
+ if (result === undefined) return {};
112
+ return {
113
+ file: await this.getLocalFileHandle(result[0])
114
+ };
115
+ }
116
+
117
+ /**
118
+ * Resolves local handle to local file path.
119
+ *
120
+ * @param handle handle to be resolved
121
+ * @private
122
+ */
123
+ private async tryResolveLocalFileHandle(handle: LocalImportFileHandle): Promise<string> {
124
+ if (isImportFileHandleIndex(handle)) {
125
+ const handleData = parseIndexHandle(handle);
126
+ const localProjection = this.localProjectionsMap.get(handleData.storageId);
127
+ if (!localProjection)
128
+ throw new Error(`Storage ${handleData.storageId} is not mounted locally.`);
129
+ return path.join(localProjection.localPath, handleData.path);
130
+ } else {
131
+ const handleData = parseUploadHandle(handle);
132
+ // checking it is a valid local handle from out machine
133
+ this.signer.verify(
134
+ handleData.localPath,
135
+ handleData.pathSignature,
136
+ 'Failed to validate local file handle signature.'
137
+ );
138
+
139
+ const localPath = handleData.localPath;
140
+
141
+ const stat = await fsp.stat(localPath, { bigint: true });
142
+ if (String(stat.mtimeMs / 1000n) !== handleData.modificationTime)
143
+ throw new Error('File has changed since the handle was created.');
144
+
145
+ return localPath;
146
+ }
147
+ }
148
+
43
149
  public async getLocalFileHandle(
44
150
  localPath: string
45
- ): Promise<sdk.ImportFileHandleUpload> {
46
- const stat = await fs.stat(localPath, { bigint: true });
47
- return createUploadHandle(
151
+ ): Promise<sdk.ImportFileHandle & LocalImportFileHandle> {
152
+ validateAbsolute(localPath);
153
+
154
+ // Checking if local path is directly reachable by pl, because it is in one of the
155
+ // locally mounted storages
156
+ for (const lp of this.localProjectionsMap.values()) {
157
+ // note: empty lp.localPath will match any address
158
+ if (localPath.startsWith(lp.localPath)) {
159
+ // Just in case:
160
+ // > path.relative("/a/b", "/a/b/c");
161
+ // 'c'
162
+ const pathWithinStorage =
163
+ lp.localPath === '' ? localPath : path.relative(lp.localPath, localPath);
164
+ return createIndexImportHandle(
165
+ lp.storageId,
166
+ pathWithinStorage
167
+ ) as sdk.ImportFileHandleIndex & LocalImportFileHandle;
168
+ }
169
+ }
170
+
171
+ // we get here if none of the local projections matched the path
172
+
173
+ const stat = await fsp.stat(localPath, { bigint: true });
174
+ return createUploadImportHandle(
48
175
  localPath,
49
176
  this.signer,
50
177
  stat.size,
51
178
  stat.mtimeMs / 1000n // integer division
52
- );
179
+ ) as sdk.ImportFileHandleUpload & LocalImportFileHandle;
53
180
  }
54
181
 
55
182
  public async getStorageList(): Promise<sdk.StorageEntry[]> {
56
- return toStorageEntry(
57
- this.localStorageToPath,
58
- await this.getAvailableStorageIds()
59
- );
183
+ return [
184
+ ...[...this.virtualStoragesMap.values()].map((vs) => ({
185
+ name: vs.name,
186
+ handle: createLocalStorageHandle(vs.name, vs.root),
187
+ initialFullPath: vs.initialPath
188
+ })),
189
+ ...Object.entries(this.storageIdToResourceId!).map(([storageId, resourceId]) => ({
190
+ name: storageId,
191
+ handle: createRemoteStorageHandle(storageId, resourceId),
192
+ initialFullPath: '' // we don't have any additional information from where to start browsing remote storages
193
+ }))
194
+ ] as sdk.StorageEntry[];
60
195
  }
61
196
 
62
197
  public async listFiles(
63
198
  storageHandle: sdk.StorageHandle,
64
- path: string
199
+ fullPath: string
65
200
  ): Promise<sdk.ListFilesResult> {
66
- const storage = fromStorageHandle(storageHandle);
201
+ const storageData = parseStorageHandle(storageHandle);
67
202
 
68
- let list: ListResponse;
69
-
70
- if (storage.remote) {
71
- list = await this.clientLs.list(storage, path);
203
+ if (storageData.isRemote) {
204
+ const response = await this.lsClient.list(storageData, fullPath);
205
+ return {
206
+ entries: response.items.map((e) => ({
207
+ type: e.isDir ? 'dir' : 'file',
208
+ name: e.name,
209
+ fullPath: e.fullName,
210
+ handle: createIndexImportHandle(storageData.name, e.fullName)
211
+ }))
212
+ };
72
213
  } else {
73
- list = await this.getLocalFiles(this.logger, storage.path, path);
74
- }
214
+ if (path.sep === '/' && fullPath === '') fullPath = '/';
75
215
 
76
- return toLsEntries({
77
- storageName: storage.name,
78
- list,
79
- signer: this.signer,
80
- remote: storage.remote
81
- });
82
- }
216
+ const lsRoot =
217
+ storageData.rootPath === ''
218
+ ? validateAbsolute(fullPath)
219
+ : path.join(storageData.rootPath, fullPath);
83
220
 
84
- private async getAvailableStorageIds() {
85
- if (this.storageIdToResourceId == undefined)
86
- this.storageIdToResourceId = await doGetAvailableStorageIds(this.client);
221
+ const entries: LsEntry[] = [];
222
+ for await (const dirent of await fsp.opendir(lsRoot)) {
223
+ if (!dirent.isFile() && !dirent.isDirectory()) continue;
87
224
 
88
- return this.storageIdToResourceId;
225
+ // We cannot use no dirent.fullPath no dirent.parentPath,
226
+ // since the former is deprecated
227
+ // and the later works differently on different versions.
228
+ const absolutePath = path.join(lsRoot, dirent.name);
229
+
230
+ entries.push({
231
+ type: dirent.isFile() ? 'file' : 'dir',
232
+ name: dirent.name,
233
+ fullPath: absolutePath,
234
+ handle: await this.getLocalFileHandle(absolutePath)
235
+ });
236
+ }
237
+
238
+ return { entries };
239
+ }
89
240
  }
90
241
 
91
- private async getLocalFiles(
242
+ public static async init(
92
243
  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
- }
244
+ client: PlClient,
245
+ signer: Signer,
246
+ virtualStorages: VirtualLocalStorageSpec[],
247
+ /** Pl storages available locally */
248
+ localProjections: LocalStorageProjection[],
249
+ openFileDialogCallback: OpenFileDialogCallback
250
+ ): Promise<LsDriver> {
251
+ const lsClient = createLsFilesClient(client, logger);
117
252
 
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
- };
253
+ // validating inputs
254
+ for (const vp of virtualStorages) validateAbsolute(vp.root);
255
+ for (const lp of localProjections) if (lp.localPath !== '') validateAbsolute(lp.localPath);
256
+
257
+ // creating indexed maps for quick access
258
+ const virtualStoragesMap = new Map(virtualStorages.map((s) => [s.name, s]));
259
+ const localProjectionsMap = new Map(localProjections.map((s) => [s.storageId, s]));
125
260
 
126
- return resp;
261
+ // validating there is no intersection
262
+ if (
263
+ new Set([...virtualStoragesMap.keys(), ...localProjectionsMap.keys()]).size !==
264
+ virtualStoragesMap.size + localProjectionsMap.size
265
+ )
266
+ throw new Error(
267
+ 'Intersection between local projection storage ids and virtual storages names detected.'
268
+ );
269
+
270
+ return new LsDriver(
271
+ logger,
272
+ lsClient,
273
+ await doGetAvailableStorageIds(client),
274
+ signer,
275
+ virtualStoragesMap,
276
+ localProjectionsMap,
277
+ openFileDialogCallback
278
+ );
127
279
  }
128
280
  }
129
281
 
130
- async function doGetAvailableStorageIds(
131
- client: PlClient
132
- ): Promise<Record<string, ResourceId>> {
282
+ async function doGetAvailableStorageIds(client: PlClient): Promise<Record<string, ResourceId>> {
133
283
  return client.withReadTx('GetAvailableStorageIds', async (tx) => {
134
284
  const lsProviderId = await tx.getResourceByName('LSProvider');
135
285
  const provider = await tx.getResourceData(lsProviderId, true);
@@ -0,0 +1,41 @@
1
+ import { z } from 'zod';
2
+
3
+ export const ImportFileHandleUploadData = z.object({
4
+ /** Local file path, to take data for upload */
5
+ localPath: z.string(),
6
+ /** Path signature, to check this data was generated by us */
7
+ pathSignature: z.string(),
8
+ /** File size in bytes */
9
+ sizeBytes: z.string(),
10
+ /** Modification time unix timestamp in seconds */
11
+ modificationTime: z.string()
12
+ });
13
+ export type ImportFileHandleUploadData = z.infer<typeof ImportFileHandleUploadData>;
14
+
15
+ export const ImportFileHandleIndexData = z.object({
16
+ /** Pl storage id */
17
+ storageId: z.string(),
18
+ /** Path inside storage */
19
+ path: z.string()
20
+ });
21
+ export type ImportFileHandleIndexData = z.infer<typeof ImportFileHandleIndexData>;
22
+
23
+ export const ImportFileHandleData = z.union([
24
+ ImportFileHandleUploadData,
25
+ ImportFileHandleIndexData
26
+ ]);
27
+ export type ImportFileHandleData = z.infer<typeof ImportFileHandleData>;
28
+
29
+ /** Defines which storages from pl are available via local paths */
30
+ export type LocalStorageProjection = {
31
+ /** Pl storage id */
32
+ readonly storageId: string;
33
+
34
+ /**
35
+ * Local path, the storage is mounted at.
36
+ *
37
+ * Empty string means that this storage accepts absolute paths, and operates inside the same OS.
38
+ * This matches the behaviour how pl interprets FS storage config.
39
+ * */
40
+ readonly localPath: string;
41
+ };