@naturalcycles/cloud-storage-lib 1.9.1 → 1.10.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/cloudStorage.d.ts +17 -7
- package/dist/cloudStorage.js +64 -19
- package/dist/commonStorage.d.ts +9 -1
- package/dist/inMemoryCommonStorage.d.ts +3 -1
- package/dist/inMemoryCommonStorage.js +10 -1
- package/package.json +1 -1
- package/src/cloudStorage.ts +126 -28
- package/src/commonStorage.ts +11 -1
- package/src/inMemoryCommonStorage.ts +15 -0
package/dist/cloudStorage.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/// <reference types="node" />
|
|
3
3
|
import { Readable, Writable } from 'node:stream';
|
|
4
4
|
import { Storage, StorageOptions } from '@google-cloud/storage';
|
|
5
|
-
import { LocalTimeInput } from '@naturalcycles/js-lib';
|
|
5
|
+
import { CommonLogger, LocalTimeInput } from '@naturalcycles/js-lib';
|
|
6
6
|
import { ReadableTyped } from '@naturalcycles/nodejs-lib';
|
|
7
7
|
import { CommonStorage, CommonStorageGetOptions, FileEntry } from './commonStorage';
|
|
8
8
|
import { GCPServiceAccount } from './model';
|
|
@@ -18,9 +18,13 @@ export { Storage, type StorageOptions, };
|
|
|
18
18
|
*/
|
|
19
19
|
export interface CloudStorageCfg {
|
|
20
20
|
/**
|
|
21
|
-
*
|
|
21
|
+
* Default is console
|
|
22
22
|
*/
|
|
23
|
-
|
|
23
|
+
logger?: CommonLogger;
|
|
24
|
+
/**
|
|
25
|
+
* Pass true for extra debugging
|
|
26
|
+
*/
|
|
27
|
+
debug?: boolean;
|
|
24
28
|
}
|
|
25
29
|
/**
|
|
26
30
|
* CloudStorage implementation of CommonStorage API.
|
|
@@ -29,13 +33,17 @@ export interface CloudStorageCfg {
|
|
|
29
33
|
*/
|
|
30
34
|
export declare class CloudStorage implements CommonStorage {
|
|
31
35
|
storage: Storage;
|
|
36
|
+
private constructor();
|
|
37
|
+
cfg: CloudStorageCfg & {
|
|
38
|
+
logger: CommonLogger;
|
|
39
|
+
};
|
|
40
|
+
static createFromGCPServiceAccount(credentials?: GCPServiceAccount, cfg?: CloudStorageCfg): CloudStorage;
|
|
41
|
+
static createFromStorageOptions(storageOptions?: StorageOptions, cfg?: CloudStorageCfg): CloudStorage;
|
|
32
42
|
/**
|
|
33
43
|
* Passing the pre-created Storage allows to instantiate it from both
|
|
34
44
|
* GCP Storage and FirebaseStorage.
|
|
35
45
|
*/
|
|
36
|
-
|
|
37
|
-
static createFromGCPServiceAccount(cfg: CloudStorageCfg): CloudStorage;
|
|
38
|
-
static createFromStorageOptions(storageOptions?: StorageOptions): CloudStorage;
|
|
46
|
+
static createFromStorage(storage: Storage, cfg?: CloudStorageCfg): CloudStorage;
|
|
39
47
|
ping(bucketName?: string): Promise<void>;
|
|
40
48
|
deletePath(bucketName: string, prefix: string): Promise<void>;
|
|
41
49
|
deletePaths(bucketName: string, prefixes: string[]): Promise<void>;
|
|
@@ -57,7 +65,9 @@ export declare class CloudStorage implements CommonStorage {
|
|
|
57
65
|
copyFile(fromBucket: string, fromPath: string, toPath: string, toBucket?: string): Promise<void>;
|
|
58
66
|
moveFile(fromBucket: string, fromPath: string, toPath: string, toBucket?: string): Promise<void>;
|
|
59
67
|
movePath(fromBucket: string, fromPrefix: string, toPrefix: string, toBucket?: string): Promise<void>;
|
|
60
|
-
|
|
68
|
+
deleteFiles(bucketName: string, filePaths: string[]): Promise<void>;
|
|
69
|
+
combineFiles(bucketName: string, filePaths: string[], toPath: string, toBucket?: string, currentRecursionDepth?: number): Promise<void>;
|
|
70
|
+
combine(bucketName: string, prefix: string, toPath: string, toBucket?: string): Promise<void>;
|
|
61
71
|
/**
|
|
62
72
|
* Acquires a "signed url", which allows bearer to use it to download ('read') the file.
|
|
63
73
|
*
|
package/dist/cloudStorage.js
CHANGED
|
@@ -4,34 +4,43 @@ exports.CloudStorage = exports.Storage = void 0;
|
|
|
4
4
|
const storage_1 = require("@google-cloud/storage");
|
|
5
5
|
Object.defineProperty(exports, "Storage", { enumerable: true, get: function () { return storage_1.Storage; } });
|
|
6
6
|
const js_lib_1 = require("@naturalcycles/js-lib");
|
|
7
|
+
const MAX_RECURSION_DEPTH = 10;
|
|
8
|
+
const BATCH_SIZE = 32;
|
|
7
9
|
/**
|
|
8
10
|
* CloudStorage implementation of CommonStorage API.
|
|
9
11
|
*
|
|
10
12
|
* API: https://googleapis.dev/nodejs/storage/latest/index.html
|
|
11
13
|
*/
|
|
12
14
|
class CloudStorage {
|
|
13
|
-
|
|
14
|
-
* Passing the pre-created Storage allows to instantiate it from both
|
|
15
|
-
* GCP Storage and FirebaseStorage.
|
|
16
|
-
*/
|
|
17
|
-
constructor(storage) {
|
|
15
|
+
constructor(storage, cfg = {}) {
|
|
18
16
|
this.storage = storage;
|
|
17
|
+
this.cfg = {
|
|
18
|
+
logger: console,
|
|
19
|
+
...cfg,
|
|
20
|
+
};
|
|
19
21
|
}
|
|
20
|
-
static createFromGCPServiceAccount(cfg) {
|
|
22
|
+
static createFromGCPServiceAccount(credentials, cfg) {
|
|
21
23
|
const storage = new storage_1.Storage({
|
|
22
|
-
credentials
|
|
24
|
+
credentials,
|
|
23
25
|
// Explicitly passing it here to fix this error:
|
|
24
26
|
// Error: Unable to detect a Project Id in the current environment.
|
|
25
27
|
// To learn more about authentication and Google APIs, visit:
|
|
26
28
|
// https://cloud.google.com/docs/authentication/getting-started
|
|
27
29
|
// at /root/repo/node_modules/google-auth-library/build/src/auth/googleauth.js:95:31
|
|
28
|
-
projectId:
|
|
30
|
+
projectId: credentials?.project_id,
|
|
29
31
|
});
|
|
30
|
-
return new CloudStorage(storage);
|
|
32
|
+
return new CloudStorage(storage, cfg);
|
|
31
33
|
}
|
|
32
|
-
static createFromStorageOptions(storageOptions) {
|
|
34
|
+
static createFromStorageOptions(storageOptions, cfg) {
|
|
33
35
|
const storage = new storage_1.Storage(storageOptions);
|
|
34
|
-
return new CloudStorage(storage);
|
|
36
|
+
return new CloudStorage(storage, cfg);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Passing the pre-created Storage allows to instantiate it from both
|
|
40
|
+
* GCP Storage and FirebaseStorage.
|
|
41
|
+
*/
|
|
42
|
+
static createFromStorage(storage, cfg) {
|
|
43
|
+
return new CloudStorage(storage, cfg);
|
|
35
44
|
}
|
|
36
45
|
async ping(bucketName) {
|
|
37
46
|
await this.storage.bucket(bucketName || 'non-existing-for-sure').exists();
|
|
@@ -88,6 +97,8 @@ class CloudStorage {
|
|
|
88
97
|
return [];
|
|
89
98
|
const [content] = await f.download();
|
|
90
99
|
return [{ filePath, content }];
|
|
100
|
+
}, {
|
|
101
|
+
concurrency: 16,
|
|
91
102
|
});
|
|
92
103
|
}
|
|
93
104
|
async getFile(bucketName, filePath) {
|
|
@@ -153,14 +164,48 @@ class CloudStorage {
|
|
|
153
164
|
await file.move(this.storage.bucket(toBucket || fromBucket).file(newName));
|
|
154
165
|
});
|
|
155
166
|
}
|
|
156
|
-
async
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
167
|
+
async deleteFiles(bucketName, filePaths) {
|
|
168
|
+
await (0, js_lib_1.pMap)(filePaths, async (filePath) => {
|
|
169
|
+
await this.storage.bucket(bucketName).file(filePath).delete();
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
async combineFiles(bucketName, filePaths, toPath, toBucket, currentRecursionDepth = 0) {
|
|
173
|
+
(0, js_lib_1._assert)(currentRecursionDepth <= MAX_RECURSION_DEPTH, `combineFiles reached max recursion depth of ${MAX_RECURSION_DEPTH}`);
|
|
174
|
+
const { logger, debug } = this.cfg;
|
|
175
|
+
if (debug) {
|
|
176
|
+
logger.log(`[${currentRecursionDepth}] Will compose ${filePaths.length} files, by batches of ${BATCH_SIZE}`);
|
|
177
|
+
}
|
|
178
|
+
const intermediateFiles = [];
|
|
179
|
+
if (filePaths.length <= BATCH_SIZE) {
|
|
180
|
+
await this.storage
|
|
181
|
+
.bucket(bucketName)
|
|
182
|
+
.combine(filePaths, this.storage.bucket(toBucket || bucketName).file(toPath));
|
|
183
|
+
if (debug) {
|
|
184
|
+
logger.log(`[${currentRecursionDepth}] Composed into ${toPath}!`);
|
|
185
|
+
}
|
|
186
|
+
await this.deleteFiles(bucketName, filePaths);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
const started = Date.now();
|
|
190
|
+
await (0, js_lib_1.pMap)((0, js_lib_1._chunk)(filePaths, BATCH_SIZE), async (fileBatch, i) => {
|
|
191
|
+
if (debug) {
|
|
192
|
+
logger.log(`[${currentRecursionDepth}] Composing batch ${i + 1}...`);
|
|
193
|
+
}
|
|
194
|
+
const intermediateFile = `temp_${currentRecursionDepth}_${i}`;
|
|
195
|
+
await this.storage
|
|
196
|
+
.bucket(bucketName)
|
|
197
|
+
.combine(fileBatch, this.storage.bucket(toBucket || bucketName).file(intermediateFile));
|
|
198
|
+
intermediateFiles.push(intermediateFile);
|
|
199
|
+
await this.deleteFiles(bucketName, fileBatch);
|
|
200
|
+
});
|
|
201
|
+
if (debug) {
|
|
202
|
+
logger.log(`[${currentRecursionDepth}] Batch composed into ${intermediateFiles.length} files, in ${(0, js_lib_1._since)(started)}`);
|
|
203
|
+
}
|
|
204
|
+
await this.combineFiles(toBucket || bucketName, intermediateFiles, toPath, toBucket, currentRecursionDepth + 1);
|
|
205
|
+
}
|
|
206
|
+
async combine(bucketName, prefix, toPath, toBucket) {
|
|
207
|
+
const filePaths = await this.getFileNames(bucketName, { prefix });
|
|
208
|
+
await this.combineFiles(bucketName, filePaths, toPath, toBucket);
|
|
164
209
|
}
|
|
165
210
|
/**
|
|
166
211
|
* Acquires a "signed url", which allows bearer to use it to download ('read') the file.
|
package/dist/commonStorage.d.ts
CHANGED
|
@@ -56,6 +56,10 @@ export interface CommonStorage {
|
|
|
56
56
|
*/
|
|
57
57
|
deletePath: (bucketName: string, prefix: string) => Promise<void>;
|
|
58
58
|
deletePaths: (bucketName: string, prefixes: string[]) => Promise<void>;
|
|
59
|
+
/**
|
|
60
|
+
* Should delete all files by their paths.
|
|
61
|
+
*/
|
|
62
|
+
deleteFiles: (bucketName: string, filePaths: string[]) => Promise<void>;
|
|
59
63
|
/**
|
|
60
64
|
* Returns an array of strings which are file paths.
|
|
61
65
|
* Files that are not found by the path are not present in the map.
|
|
@@ -95,7 +99,11 @@ export interface CommonStorage {
|
|
|
95
99
|
*
|
|
96
100
|
* @experimental
|
|
97
101
|
*/
|
|
98
|
-
|
|
102
|
+
combineFiles: (bucketName: string, filePaths: string[], toPath: string, toBucket?: string) => Promise<void>;
|
|
103
|
+
/**
|
|
104
|
+
* Like `combineFiles`, but for a `prefix`.
|
|
105
|
+
*/
|
|
106
|
+
combine: (bucketName: string, prefix: string, toPath: string, toBucket?: string) => Promise<void>;
|
|
99
107
|
/**
|
|
100
108
|
* Acquire a "signed url", which allows bearer to use it to download ('read') the file.
|
|
101
109
|
*
|
|
@@ -18,6 +18,7 @@ export declare class InMemoryCommonStorage implements CommonStorage {
|
|
|
18
18
|
saveFile(bucketName: string, filePath: string, content: Buffer): Promise<void>;
|
|
19
19
|
deletePath(bucketName: string, prefix: string): Promise<void>;
|
|
20
20
|
deletePaths(bucketName: string, prefixes: string[]): Promise<void>;
|
|
21
|
+
deleteFiles(bucketName: string, filePaths: string[]): Promise<void>;
|
|
21
22
|
getFileNames(bucketName: string, opt?: CommonStorageGetOptions): Promise<string[]>;
|
|
22
23
|
getFileNamesStream(bucketName: string, opt?: CommonStorageGetOptions): ReadableTyped<string>;
|
|
23
24
|
getFilesStream(bucketName: string, opt?: CommonStorageGetOptions): ReadableTyped<FileEntry>;
|
|
@@ -29,6 +30,7 @@ export declare class InMemoryCommonStorage implements CommonStorage {
|
|
|
29
30
|
copyFile(fromBucket: string, fromPath: string, toPath: string, toBucket?: string): Promise<void>;
|
|
30
31
|
moveFile(fromBucket: string, fromPath: string, toPath: string, toBucket?: string): Promise<void>;
|
|
31
32
|
movePath(fromBucket: string, fromPrefix: string, toPrefix: string, toBucket?: string): Promise<void>;
|
|
32
|
-
combine(bucketName: string,
|
|
33
|
+
combine(bucketName: string, prefix: string, toPath: string, toBucket?: string): Promise<void>;
|
|
34
|
+
combineFiles(bucketName: string, filePaths: string[], toPath: string, toBucket?: string): Promise<void>;
|
|
33
35
|
getSignedUrl(bucketName: string, filePath: string, expires: LocalTimeInput): Promise<string>;
|
|
34
36
|
}
|
|
@@ -39,6 +39,11 @@ class InMemoryCommonStorage {
|
|
|
39
39
|
}
|
|
40
40
|
});
|
|
41
41
|
}
|
|
42
|
+
async deleteFiles(bucketName, filePaths) {
|
|
43
|
+
if (!this.data[bucketName])
|
|
44
|
+
return;
|
|
45
|
+
filePaths.forEach(filePath => delete this.data[bucketName][filePath]);
|
|
46
|
+
}
|
|
42
47
|
async getFileNames(bucketName, opt = {}) {
|
|
43
48
|
const { prefix = '', fullPaths = true } = opt;
|
|
44
49
|
return Object.keys(this.data[bucketName] || {})
|
|
@@ -104,7 +109,11 @@ class InMemoryCommonStorage {
|
|
|
104
109
|
delete this.data[fromBucket][filePath];
|
|
105
110
|
});
|
|
106
111
|
}
|
|
107
|
-
async combine(bucketName,
|
|
112
|
+
async combine(bucketName, prefix, toPath, toBucket) {
|
|
113
|
+
const filePaths = await this.getFileNames(bucketName, { prefix });
|
|
114
|
+
await this.combineFiles(bucketName, filePaths, toPath, toBucket);
|
|
115
|
+
}
|
|
116
|
+
async combineFiles(bucketName, filePaths, toPath, toBucket) {
|
|
108
117
|
if (!this.data[bucketName])
|
|
109
118
|
return;
|
|
110
119
|
const tob = toBucket || bucketName;
|
package/package.json
CHANGED
package/src/cloudStorage.ts
CHANGED
|
@@ -2,7 +2,10 @@ import { Readable, Writable } from 'node:stream'
|
|
|
2
2
|
import { File, Storage, StorageOptions } from '@google-cloud/storage'
|
|
3
3
|
import {
|
|
4
4
|
_assert,
|
|
5
|
+
_chunk,
|
|
6
|
+
_since,
|
|
5
7
|
_substringAfterLast,
|
|
8
|
+
CommonLogger,
|
|
6
9
|
localTime,
|
|
7
10
|
LocalTimeInput,
|
|
8
11
|
pMap,
|
|
@@ -18,6 +21,9 @@ export {
|
|
|
18
21
|
type StorageOptions,
|
|
19
22
|
}
|
|
20
23
|
|
|
24
|
+
const MAX_RECURSION_DEPTH = 10
|
|
25
|
+
const BATCH_SIZE = 32
|
|
26
|
+
|
|
21
27
|
/**
|
|
22
28
|
* This object is intentionally made to NOT extend StorageOptions,
|
|
23
29
|
* because StorageOptions is complicated and provides just too many ways
|
|
@@ -29,9 +35,14 @@ export {
|
|
|
29
35
|
*/
|
|
30
36
|
export interface CloudStorageCfg {
|
|
31
37
|
/**
|
|
32
|
-
*
|
|
38
|
+
* Default is console
|
|
39
|
+
*/
|
|
40
|
+
logger?: CommonLogger
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Pass true for extra debugging
|
|
33
44
|
*/
|
|
34
|
-
|
|
45
|
+
debug?: boolean
|
|
35
46
|
}
|
|
36
47
|
|
|
37
48
|
/**
|
|
@@ -40,29 +51,51 @@ export interface CloudStorageCfg {
|
|
|
40
51
|
* API: https://googleapis.dev/nodejs/storage/latest/index.html
|
|
41
52
|
*/
|
|
42
53
|
export class CloudStorage implements CommonStorage {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
54
|
+
private constructor(
|
|
55
|
+
public storage: Storage,
|
|
56
|
+
cfg: CloudStorageCfg = {},
|
|
57
|
+
) {
|
|
58
|
+
this.cfg = {
|
|
59
|
+
logger: console,
|
|
60
|
+
...cfg,
|
|
61
|
+
}
|
|
62
|
+
}
|
|
48
63
|
|
|
49
|
-
|
|
64
|
+
cfg: CloudStorageCfg & {
|
|
65
|
+
logger: CommonLogger
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
static createFromGCPServiceAccount(
|
|
69
|
+
credentials?: GCPServiceAccount,
|
|
70
|
+
cfg?: CloudStorageCfg,
|
|
71
|
+
): CloudStorage {
|
|
50
72
|
const storage = new Storage({
|
|
51
|
-
credentials
|
|
73
|
+
credentials,
|
|
52
74
|
// Explicitly passing it here to fix this error:
|
|
53
75
|
// Error: Unable to detect a Project Id in the current environment.
|
|
54
76
|
// To learn more about authentication and Google APIs, visit:
|
|
55
77
|
// https://cloud.google.com/docs/authentication/getting-started
|
|
56
78
|
// at /root/repo/node_modules/google-auth-library/build/src/auth/googleauth.js:95:31
|
|
57
|
-
projectId:
|
|
79
|
+
projectId: credentials?.project_id,
|
|
58
80
|
})
|
|
59
81
|
|
|
60
|
-
return new CloudStorage(storage)
|
|
82
|
+
return new CloudStorage(storage, cfg)
|
|
61
83
|
}
|
|
62
84
|
|
|
63
|
-
static createFromStorageOptions(
|
|
85
|
+
static createFromStorageOptions(
|
|
86
|
+
storageOptions?: StorageOptions,
|
|
87
|
+
cfg?: CloudStorageCfg,
|
|
88
|
+
): CloudStorage {
|
|
64
89
|
const storage = new Storage(storageOptions)
|
|
65
|
-
return new CloudStorage(storage)
|
|
90
|
+
return new CloudStorage(storage, cfg)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Passing the pre-created Storage allows to instantiate it from both
|
|
95
|
+
* GCP Storage and FirebaseStorage.
|
|
96
|
+
*/
|
|
97
|
+
static createFromStorage(storage: Storage, cfg?: CloudStorageCfg): CloudStorage {
|
|
98
|
+
return new CloudStorage(storage, cfg)
|
|
66
99
|
}
|
|
67
100
|
|
|
68
101
|
async ping(bucketName?: string): Promise<void> {
|
|
@@ -128,13 +161,18 @@ export class CloudStorage implements CommonStorage {
|
|
|
128
161
|
prefix,
|
|
129
162
|
maxResults: opt.limit || undefined,
|
|
130
163
|
}) as ReadableTyped<File>
|
|
131
|
-
).flatMap(
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
164
|
+
).flatMap(
|
|
165
|
+
async f => {
|
|
166
|
+
const filePath = this.normalizeFilename(f.name, fullPaths)
|
|
167
|
+
if (filePath === SKIP) return []
|
|
168
|
+
|
|
169
|
+
const [content] = await f.download()
|
|
170
|
+
return [{ filePath, content }] as FileEntry[]
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
concurrency: 16,
|
|
174
|
+
},
|
|
175
|
+
)
|
|
138
176
|
}
|
|
139
177
|
|
|
140
178
|
async getFile(bucketName: string, filePath: string): Promise<Buffer | null> {
|
|
@@ -230,21 +268,81 @@ export class CloudStorage implements CommonStorage {
|
|
|
230
268
|
})
|
|
231
269
|
}
|
|
232
270
|
|
|
233
|
-
async
|
|
271
|
+
async deleteFiles(bucketName: string, filePaths: string[]): Promise<void> {
|
|
272
|
+
await pMap(filePaths, async filePath => {
|
|
273
|
+
await this.storage.bucket(bucketName).file(filePath).delete()
|
|
274
|
+
})
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
async combineFiles(
|
|
234
278
|
bucketName: string,
|
|
235
279
|
filePaths: string[],
|
|
236
280
|
toPath: string,
|
|
237
281
|
toBucket?: string,
|
|
282
|
+
currentRecursionDepth = 0, // not to be set publicly, only used internally
|
|
238
283
|
): Promise<void> {
|
|
239
|
-
|
|
240
|
-
|
|
284
|
+
_assert(
|
|
285
|
+
currentRecursionDepth <= MAX_RECURSION_DEPTH,
|
|
286
|
+
`combineFiles reached max recursion depth of ${MAX_RECURSION_DEPTH}`,
|
|
287
|
+
)
|
|
288
|
+
const { logger, debug } = this.cfg
|
|
289
|
+
|
|
290
|
+
if (debug) {
|
|
291
|
+
logger.log(
|
|
292
|
+
`[${currentRecursionDepth}] Will compose ${filePaths.length} files, by batches of ${BATCH_SIZE}`,
|
|
293
|
+
)
|
|
294
|
+
}
|
|
241
295
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
296
|
+
const intermediateFiles: string[] = []
|
|
297
|
+
|
|
298
|
+
if (filePaths.length <= BATCH_SIZE) {
|
|
299
|
+
await this.storage
|
|
300
|
+
.bucket(bucketName)
|
|
301
|
+
.combine(filePaths, this.storage.bucket(toBucket || bucketName).file(toPath))
|
|
302
|
+
|
|
303
|
+
if (debug) {
|
|
304
|
+
logger.log(`[${currentRecursionDepth}] Composed into ${toPath}!`)
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
await this.deleteFiles(bucketName, filePaths)
|
|
308
|
+
return
|
|
309
|
+
}
|
|
245
310
|
|
|
246
|
-
|
|
247
|
-
await
|
|
311
|
+
const started = Date.now()
|
|
312
|
+
await pMap(_chunk(filePaths, BATCH_SIZE), async (fileBatch, i) => {
|
|
313
|
+
if (debug) {
|
|
314
|
+
logger.log(`[${currentRecursionDepth}] Composing batch ${i + 1}...`)
|
|
315
|
+
}
|
|
316
|
+
const intermediateFile = `temp_${currentRecursionDepth}_${i}`
|
|
317
|
+
await this.storage
|
|
318
|
+
.bucket(bucketName)
|
|
319
|
+
.combine(fileBatch, this.storage.bucket(toBucket || bucketName).file(intermediateFile))
|
|
320
|
+
intermediateFiles.push(intermediateFile)
|
|
321
|
+
await this.deleteFiles(bucketName, fileBatch)
|
|
322
|
+
})
|
|
323
|
+
if (debug) {
|
|
324
|
+
logger.log(
|
|
325
|
+
`[${currentRecursionDepth}] Batch composed into ${intermediateFiles.length} files, in ${_since(started)}`,
|
|
326
|
+
)
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
await this.combineFiles(
|
|
330
|
+
toBucket || bucketName,
|
|
331
|
+
intermediateFiles,
|
|
332
|
+
toPath,
|
|
333
|
+
toBucket,
|
|
334
|
+
currentRecursionDepth + 1,
|
|
335
|
+
)
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
async combine(
|
|
339
|
+
bucketName: string,
|
|
340
|
+
prefix: string,
|
|
341
|
+
toPath: string,
|
|
342
|
+
toBucket?: string,
|
|
343
|
+
): Promise<void> {
|
|
344
|
+
const filePaths = await this.getFileNames(bucketName, { prefix })
|
|
345
|
+
await this.combineFiles(bucketName, filePaths, toPath, toBucket)
|
|
248
346
|
}
|
|
249
347
|
|
|
250
348
|
/**
|
package/src/commonStorage.ts
CHANGED
|
@@ -69,6 +69,11 @@ export interface CommonStorage {
|
|
|
69
69
|
|
|
70
70
|
deletePaths: (bucketName: string, prefixes: string[]) => Promise<void>
|
|
71
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Should delete all files by their paths.
|
|
74
|
+
*/
|
|
75
|
+
deleteFiles: (bucketName: string, filePaths: string[]) => Promise<void>
|
|
76
|
+
|
|
72
77
|
/**
|
|
73
78
|
* Returns an array of strings which are file paths.
|
|
74
79
|
* Files that are not found by the path are not present in the map.
|
|
@@ -134,13 +139,18 @@ export interface CommonStorage {
|
|
|
134
139
|
*
|
|
135
140
|
* @experimental
|
|
136
141
|
*/
|
|
137
|
-
|
|
142
|
+
combineFiles: (
|
|
138
143
|
bucketName: string,
|
|
139
144
|
filePaths: string[],
|
|
140
145
|
toPath: string,
|
|
141
146
|
toBucket?: string,
|
|
142
147
|
) => Promise<void>
|
|
143
148
|
|
|
149
|
+
/**
|
|
150
|
+
* Like `combineFiles`, but for a `prefix`.
|
|
151
|
+
*/
|
|
152
|
+
combine: (bucketName: string, prefix: string, toPath: string, toBucket?: string) => Promise<void>
|
|
153
|
+
|
|
144
154
|
/**
|
|
145
155
|
* Acquire a "signed url", which allows bearer to use it to download ('read') the file.
|
|
146
156
|
*
|
|
@@ -54,6 +54,11 @@ export class InMemoryCommonStorage implements CommonStorage {
|
|
|
54
54
|
})
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
async deleteFiles(bucketName: string, filePaths: string[]): Promise<void> {
|
|
58
|
+
if (!this.data[bucketName]) return
|
|
59
|
+
filePaths.forEach(filePath => delete this.data[bucketName]![filePath])
|
|
60
|
+
}
|
|
61
|
+
|
|
57
62
|
async getFileNames(bucketName: string, opt: CommonStorageGetOptions = {}): Promise<string[]> {
|
|
58
63
|
const { prefix = '', fullPaths = true } = opt
|
|
59
64
|
return Object.keys(this.data[bucketName] || {})
|
|
@@ -156,6 +161,16 @@ export class InMemoryCommonStorage implements CommonStorage {
|
|
|
156
161
|
}
|
|
157
162
|
|
|
158
163
|
async combine(
|
|
164
|
+
bucketName: string,
|
|
165
|
+
prefix: string,
|
|
166
|
+
toPath: string,
|
|
167
|
+
toBucket?: string,
|
|
168
|
+
): Promise<void> {
|
|
169
|
+
const filePaths = await this.getFileNames(bucketName, { prefix })
|
|
170
|
+
await this.combineFiles(bucketName, filePaths, toPath, toBucket)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async combineFiles(
|
|
159
174
|
bucketName: string,
|
|
160
175
|
filePaths: string[],
|
|
161
176
|
toPath: string,
|