@itwin/workspace-editor 3.3.0-dev.4 → 3.3.0-dev.42
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/README.md +466 -69
- package/lib/WorkspaceEditor.js +483 -142
- package/package.json +15 -14
- package/.rush/temp/package-deps_build +0 -0
- package/.rush/temp/package-deps_cover.json +0 -13
- package/.rush/temp/package-deps_docs.json +0 -13
- package/.rush/temp/package-deps_lint.json +0 -13
- package/.rush/temp/package-deps_webpack +0 -0
- package/.rush/temp/shrinkwrap-deps.json +0 -178
- package/CHANGELOG.json +0 -59
- package/CHANGELOG.md +0 -46
- package/lib/WorkspaceEditor.d.ts +0 -2
- package/lib/WorkspaceEditor.d.ts.map +0 -1
- package/lib/WorkspaceEditor.js.map +0 -1
- package/lib/tsconfig.tsbuildinfo +0 -1
- package/src/WorkspaceEditor.ts +0 -366
- package/tsconfig.json +0 -7
- package/workspace-editor.build.log +0 -0
package/lib/WorkspaceEditor.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
1
2
|
"use strict";
|
|
2
3
|
/*---------------------------------------------------------------------------------------------
|
|
3
4
|
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
|
|
@@ -11,69 +12,152 @@ const readline = require("readline");
|
|
|
11
12
|
const Yargs = require("yargs");
|
|
12
13
|
const core_backend_1 = require("@itwin/core-backend");
|
|
13
14
|
const core_bentley_1 = require("@itwin/core-bentley");
|
|
15
|
+
const core_common_1 = require("@itwin/core-common");
|
|
16
|
+
// cspell:ignore nodir nocase
|
|
17
|
+
/* eslint-disable id-blacklist,no-console */
|
|
18
|
+
/** Currently executing an "@" script? */
|
|
19
|
+
let inScript = false;
|
|
20
|
+
let logTimer;
|
|
21
|
+
async function askQuestion(query) {
|
|
22
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
23
|
+
return new Promise((resolve) => rl.question(query, (ans) => { rl.close(); resolve(ans); }));
|
|
24
|
+
}
|
|
25
|
+
function flushLog() {
|
|
26
|
+
core_backend_1.IModelHost.platform.flushLog();
|
|
27
|
+
}
|
|
28
|
+
/** show a message, potentially flushing log messages first */
|
|
29
|
+
function showMessage(msg) {
|
|
30
|
+
if (logTimer)
|
|
31
|
+
flushLog();
|
|
32
|
+
console.log(msg);
|
|
33
|
+
}
|
|
34
|
+
/** perform a vacuum on a database, with a message while it's happening */
|
|
35
|
+
function doVacuum(fileName, cloudContainer) {
|
|
36
|
+
process.stdout.write(`Vacuuming ${fileName} ... `);
|
|
37
|
+
core_backend_1.IModelHost.platform.DgnDb.vacuum(fileName, cloudContainer);
|
|
38
|
+
process.stdout.write("done");
|
|
39
|
+
showMessage("");
|
|
40
|
+
}
|
|
41
|
+
/** Convert a file size byte value to a friendly string, rounding to nearest integer for K, M, G, T */
|
|
42
|
+
function friendlyFileSize(size) {
|
|
43
|
+
if (size < 1024)
|
|
44
|
+
return `${size}`; // less than 1K, just show number of bytes with no units
|
|
45
|
+
const units = Math.floor(Math.log(size) / Math.log(1024));
|
|
46
|
+
return `${Math.round(size / Math.pow(1024, units))}${["", "K", "M", "G", "T"][units]}`;
|
|
47
|
+
}
|
|
14
48
|
/** Create a new empty WorkspaceDb */
|
|
15
49
|
async function createWorkspaceDb(args) {
|
|
16
|
-
|
|
17
|
-
wsFile.
|
|
18
|
-
|
|
19
|
-
wsFile.
|
|
50
|
+
args.writeable = true;
|
|
51
|
+
const wsFile = new core_backend_1.EditableWorkspaceDb(args, core_backend_1.IModelHost.appWorkspace.getContainer(args, args));
|
|
52
|
+
await wsFile.createDb();
|
|
53
|
+
showMessage(`created WorkspaceDb ${wsFile.sqliteDb.nativeDb.getFilePath()}`);
|
|
54
|
+
await wsFile.close();
|
|
20
55
|
}
|
|
21
56
|
/** open, call a function to process, then close a WorkspaceDb */
|
|
22
|
-
function processWorkspace(args, ws, fn) {
|
|
57
|
+
async function processWorkspace(args, ws, fn) {
|
|
23
58
|
ws.open();
|
|
24
|
-
|
|
59
|
+
showMessage(`WorkspaceDb [${ws.sqliteDb.nativeDb.getFilePath()}]`);
|
|
25
60
|
try {
|
|
26
|
-
fn(ws, args);
|
|
61
|
+
await fn(ws, args);
|
|
27
62
|
}
|
|
28
63
|
finally {
|
|
29
|
-
ws.close();
|
|
64
|
+
await ws.close();
|
|
30
65
|
}
|
|
31
66
|
}
|
|
67
|
+
/** get a WorkspaceContainer that may or may not be a cloud container. */
|
|
68
|
+
function getContainer(args) {
|
|
69
|
+
args.writeable = true;
|
|
70
|
+
return core_backend_1.IModelHost.appWorkspace.getContainer(args, args);
|
|
71
|
+
}
|
|
72
|
+
/** get a WorkspaceContainer that is expected to be a cloud container, throw otherwise. */
|
|
73
|
+
function getCloudContainer(args) {
|
|
74
|
+
args.syncOnConnect = true;
|
|
75
|
+
const container = getContainer(args);
|
|
76
|
+
const cloudContainer = container.cloudContainer;
|
|
77
|
+
if (!cloudContainer || !cloudContainer.isConnected)
|
|
78
|
+
throw new Error("no cloud container");
|
|
79
|
+
return cloudContainer;
|
|
80
|
+
}
|
|
81
|
+
function fixVersionArg(args) {
|
|
82
|
+
var _a;
|
|
83
|
+
const dbParts = core_backend_1.ITwinWorkspaceContainer.parseDbFileName(args.dbName);
|
|
84
|
+
args.dbName = dbParts.dbName;
|
|
85
|
+
args.version = (_a = args.version) !== null && _a !== void 0 ? _a : dbParts.version;
|
|
86
|
+
args.dbFileName = core_backend_1.ITwinWorkspaceContainer.makeDbFileName(dbParts.dbName, dbParts.version);
|
|
87
|
+
}
|
|
32
88
|
/** Open for write, call a function to process, then close a WorkspaceDb */
|
|
33
|
-
function editWorkspace(args, fn) {
|
|
34
|
-
|
|
89
|
+
async function editWorkspace(args, fn) {
|
|
90
|
+
var _a;
|
|
91
|
+
fixVersionArg(args);
|
|
92
|
+
const ws = new core_backend_1.EditableWorkspaceDb(args, getContainer(args));
|
|
93
|
+
const cloudContainer = ws.container.cloudContainer;
|
|
94
|
+
if (cloudContainer && ((_a = cloudContainer.queryDatabase(ws.dbFileName)) === null || _a === void 0 ? void 0 : _a.state) !== "copied")
|
|
95
|
+
throw new Error(`${args.dbFileName} is not editable. Create a new version first`);
|
|
96
|
+
await processWorkspace(args, ws, fn);
|
|
35
97
|
}
|
|
36
98
|
/** Open for read, call a function to process, then close a WorkspaceDb */
|
|
37
|
-
function readWorkspace(args, fn) {
|
|
38
|
-
|
|
99
|
+
async function readWorkspace(args, fn) {
|
|
100
|
+
fixVersionArg(args);
|
|
101
|
+
return processWorkspace(args, new core_backend_1.ITwinWorkspaceDb(args, getContainer(args)), fn);
|
|
39
102
|
}
|
|
40
103
|
/** List the contents of a WorkspaceDb */
|
|
41
104
|
async function listWorkspaceDb(args) {
|
|
42
|
-
readWorkspace(args, (file, args) => {
|
|
105
|
+
await readWorkspace(args, async (file, args) => {
|
|
106
|
+
const cloudContainer = file.container.cloudContainer;
|
|
107
|
+
const timer = new core_bentley_1.StopWatch("list", true);
|
|
108
|
+
let prefetch;
|
|
109
|
+
if (args.prefetch && cloudContainer) {
|
|
110
|
+
console.log(`start prefetch`);
|
|
111
|
+
prefetch = file.prefetch({ nRequests: args.nRequests });
|
|
112
|
+
}
|
|
43
113
|
if (!args.strings && !args.blobs && !args.files)
|
|
44
114
|
args.blobs = args.files = args.strings = true;
|
|
115
|
+
const nameAndSize = (stmt, size, info) => showMessage(` name=${stmt.getValueString(0)}, size=${friendlyFileSize(size !== null && size !== void 0 ? size : stmt.getValueInteger(1))}${info !== null && info !== void 0 ? info : ""}`);
|
|
45
116
|
if (args.strings) {
|
|
46
|
-
|
|
47
|
-
file.sqliteDb.withSqliteStatement("SELECT id,value FROM strings", (stmt) => {
|
|
117
|
+
showMessage(" strings:");
|
|
118
|
+
file.sqliteDb.withSqliteStatement("SELECT id,LENGTH(value) FROM strings ORDER BY id COLLATE NOCASE", (stmt) => {
|
|
48
119
|
while (core_bentley_1.DbResult.BE_SQLITE_ROW === stmt.step())
|
|
49
|
-
|
|
120
|
+
nameAndSize(stmt);
|
|
50
121
|
});
|
|
51
122
|
}
|
|
52
123
|
if (args.blobs) {
|
|
53
|
-
|
|
54
|
-
file.sqliteDb.withSqliteStatement("SELECT id,value FROM blobs", (stmt) => {
|
|
124
|
+
showMessage(" blobs:");
|
|
125
|
+
file.sqliteDb.withSqliteStatement("SELECT id,LENGTH(value) FROM blobs ORDER BY id COLLATE NOCASE", (stmt) => {
|
|
55
126
|
while (core_bentley_1.DbResult.BE_SQLITE_ROW === stmt.step())
|
|
56
|
-
|
|
127
|
+
nameAndSize(stmt);
|
|
57
128
|
});
|
|
58
129
|
}
|
|
59
130
|
if (args.files) {
|
|
60
|
-
|
|
61
|
-
file.sqliteDb.withSqliteStatement("SELECT name FROM be_EmbedFile", (stmt) => {
|
|
131
|
+
showMessage(" files:");
|
|
132
|
+
file.sqliteDb.withSqliteStatement("SELECT name FROM be_EmbedFile ORDER BY name COLLATE NOCASE", (stmt) => {
|
|
62
133
|
while (core_bentley_1.DbResult.BE_SQLITE_ROW === stmt.step()) {
|
|
63
134
|
const embed = file.queryFileResource(stmt.getValueString(0));
|
|
64
135
|
if (embed) {
|
|
65
136
|
const info = embed.info;
|
|
66
137
|
const date = new Date(info.date);
|
|
67
|
-
|
|
138
|
+
nameAndSize(stmt, info.size, `, ext="${info.fileExt}", date=${date.toString()}`);
|
|
68
139
|
}
|
|
69
140
|
}
|
|
70
141
|
});
|
|
71
142
|
}
|
|
143
|
+
if (prefetch) {
|
|
144
|
+
showMessage(`time = ${timer.elapsedSeconds.toString()}`);
|
|
145
|
+
// await askQuestion("press any key");
|
|
146
|
+
// const queryTimer = new StopWatch("list", true);
|
|
147
|
+
// showMessage(`start query`);
|
|
148
|
+
// file.sqliteDb.withSqliteStatement("SELECT id,value FROM blobs ORDER BY id COLLATE NOCASE", (stmt) => {
|
|
149
|
+
// while (DbResult.BE_SQLITE_ROW === stmt.step()) {
|
|
150
|
+
// }
|
|
151
|
+
// });
|
|
152
|
+
// showMessage(`done query, time= ${queryTimer.elapsedSeconds.toString()}`);
|
|
153
|
+
const done = await prefetch.promise;
|
|
154
|
+
showMessage(`prefetch time = ${timer.elapsedSeconds.toString()}, done=${done}`);
|
|
155
|
+
}
|
|
72
156
|
});
|
|
73
157
|
}
|
|
74
|
-
/** Add
|
|
158
|
+
/** Add files into a WorkspaceDb. */
|
|
75
159
|
async function addResource(args) {
|
|
76
|
-
editWorkspace(args, (wsFile, args) => {
|
|
160
|
+
return editWorkspace(args, async (wsFile, args) => {
|
|
77
161
|
var _a;
|
|
78
162
|
glob.sync(args.files, { cwd: (_a = args.root) !== null && _a !== void 0 ? _a : process.cwd(), nodir: true }).forEach((filePath) => {
|
|
79
163
|
var _a;
|
|
@@ -84,26 +168,56 @@ async function addResource(args) {
|
|
|
84
168
|
try {
|
|
85
169
|
if (args.type === "string") {
|
|
86
170
|
const val = fs.readFileSync(file, "utf-8");
|
|
87
|
-
wsFile
|
|
171
|
+
wsFile.addString(name, val);
|
|
88
172
|
}
|
|
89
173
|
else if (args.type === "blob") {
|
|
90
174
|
const val = fs.readFileSync(file);
|
|
91
|
-
wsFile
|
|
175
|
+
wsFile.addBlob(name, val);
|
|
92
176
|
}
|
|
93
177
|
else {
|
|
94
|
-
wsFile
|
|
178
|
+
wsFile.addFile(name, file);
|
|
95
179
|
}
|
|
96
|
-
|
|
180
|
+
showMessage(` added "${file}" as ${args.type} resource [${name}]`);
|
|
97
181
|
}
|
|
98
182
|
catch (e) {
|
|
99
|
-
console.error(e
|
|
183
|
+
console.error(core_common_1.IModelError.getErrorMessage(e));
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
/** Replace files in a WorkspaceDb. */
|
|
189
|
+
async function replaceResource(args) {
|
|
190
|
+
return editWorkspace(args, async (wsFile, args) => {
|
|
191
|
+
var _a;
|
|
192
|
+
glob.sync(args.files, { cwd: (_a = args.root) !== null && _a !== void 0 ? _a : process.cwd(), nodir: true }).forEach((filePath) => {
|
|
193
|
+
var _a;
|
|
194
|
+
const file = args.root ? (0, path_1.join)(args.root, filePath) : filePath;
|
|
195
|
+
if (!core_backend_1.IModelJsFs.existsSync(file))
|
|
196
|
+
throw new Error(`file [${file}] does not exist`);
|
|
197
|
+
const name = (_a = args.rscName) !== null && _a !== void 0 ? _a : filePath;
|
|
198
|
+
try {
|
|
199
|
+
if (args.type === "string") {
|
|
200
|
+
const val = fs.readFileSync(file, "utf-8");
|
|
201
|
+
wsFile.updateString(name, val);
|
|
202
|
+
}
|
|
203
|
+
else if (args.type === "blob") {
|
|
204
|
+
const val = fs.readFileSync(file);
|
|
205
|
+
wsFile.updateBlob(name, val);
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
wsFile.updateFile(name, file);
|
|
209
|
+
}
|
|
210
|
+
showMessage(` updated "${file}" as ${args.type} resource [${name}]`);
|
|
211
|
+
}
|
|
212
|
+
catch (e) {
|
|
213
|
+
console.error(core_common_1.IModelError.getErrorMessage(e));
|
|
100
214
|
}
|
|
101
215
|
});
|
|
102
216
|
});
|
|
103
217
|
}
|
|
104
218
|
/** Extract a single resource from a WorkspaceDb into a local file */
|
|
105
219
|
async function extractResource(args) {
|
|
106
|
-
readWorkspace(args, (file, args) => {
|
|
220
|
+
return readWorkspace(args, async (file, args) => {
|
|
107
221
|
const verify = (val) => {
|
|
108
222
|
if (val === undefined)
|
|
109
223
|
throw new Error(` ${args.type} resource "${args.rscName}" does not exist`);
|
|
@@ -118,35 +232,35 @@ async function extractResource(args) {
|
|
|
118
232
|
else {
|
|
119
233
|
verify(file.getFile(args.rscName, args.fileName));
|
|
120
234
|
}
|
|
121
|
-
|
|
235
|
+
showMessage(` ${args.type} resource [${args.rscName}] extracted to "${args.fileName}"`);
|
|
122
236
|
});
|
|
123
237
|
}
|
|
124
238
|
/** Remove a single resource from a WorkspaceDb */
|
|
125
239
|
async function removeResource(args) {
|
|
126
|
-
editWorkspace(args, (wsFile, args) => {
|
|
240
|
+
return editWorkspace(args, async (wsFile, args) => {
|
|
127
241
|
if (args.type === "string")
|
|
128
242
|
wsFile.removeString(args.rscName);
|
|
129
243
|
else if (args.type === "blob")
|
|
130
244
|
wsFile.removeBlob(args.rscName);
|
|
131
245
|
else
|
|
132
246
|
wsFile.removeFile(args.rscName);
|
|
133
|
-
|
|
247
|
+
showMessage(` removed ${args.type} resource [${args.rscName}]`);
|
|
134
248
|
});
|
|
135
249
|
}
|
|
136
|
-
|
|
137
|
-
return new core_backend_1.ITwinWorkspaceDb(args.dbName, core_backend_1.IModelHost.appWorkspace.getContainer(args)).localFile;
|
|
138
|
-
}
|
|
139
|
-
/** Vacuum a local WorkspaceDb file, usually immediately prior to uploading. */
|
|
250
|
+
/** Vacuum a WorkspaceDb. */
|
|
140
251
|
async function vacuumWorkspaceDb(args) {
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
252
|
+
const container = getContainer(args);
|
|
253
|
+
fixVersionArg(args);
|
|
254
|
+
const localFile = new core_backend_1.ITwinWorkspaceDb(args, container).dbFileName;
|
|
255
|
+
doVacuum(localFile, container.cloudContainer);
|
|
144
256
|
}
|
|
145
|
-
/** Either upload or download a WorkspaceDb to/from a WorkspaceContainer. Shows progress % during transfer */
|
|
146
|
-
async function performTransfer(direction, args) {
|
|
147
|
-
|
|
148
|
-
const
|
|
149
|
-
|
|
257
|
+
/** Either upload or download a WorkspaceDb to/from a cloud WorkspaceContainer. Shows progress % during transfer */
|
|
258
|
+
async function performTransfer(container, direction, args) {
|
|
259
|
+
fixVersionArg(args);
|
|
260
|
+
const localFileName = args.localFileName;
|
|
261
|
+
if (direction === "upload" && !args.noVacuum)
|
|
262
|
+
doVacuum(localFileName);
|
|
263
|
+
const info = `${direction === "download" ? "export" : "import"} ${localFileName}, container=${args.containerId}, dbName=${args.dbFileName} : `;
|
|
150
264
|
let last = 0;
|
|
151
265
|
const onProgress = (loaded, total) => {
|
|
152
266
|
if (loaded > last) {
|
|
@@ -159,125 +273,352 @@ async function performTransfer(direction, args) {
|
|
|
159
273
|
};
|
|
160
274
|
process.stdout.write(info);
|
|
161
275
|
const timer = new core_bentley_1.StopWatch(direction, true);
|
|
162
|
-
|
|
276
|
+
args.dbName = args.dbFileName;
|
|
277
|
+
await core_backend_1.CloudSqlite.transferDb(direction, container, { ...args, localFileName, onProgress });
|
|
163
278
|
readline.cursorTo(process.stdout, info.length);
|
|
164
279
|
process.stdout.write(`complete, ${timer.elapsedSeconds.toString()} seconds`);
|
|
280
|
+
showMessage("");
|
|
165
281
|
}
|
|
166
|
-
/**
|
|
167
|
-
async function
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
282
|
+
/** import a WorkspaceDb to a cloud WorkspaceContainer. */
|
|
283
|
+
async function importWorkspaceDb(args) {
|
|
284
|
+
var _a;
|
|
285
|
+
const container = getCloudContainer(args);
|
|
286
|
+
if ("" === (0, path_1.extname)(args.localFileName))
|
|
287
|
+
args.localFileName = `${args.localFileName}.${core_backend_1.ITwinWorkspaceDb.fileExt}`;
|
|
288
|
+
if (!core_backend_1.IModelJsFs.existsSync(args.localFileName))
|
|
289
|
+
args.localFileName = (0, path_1.join)((_a = args.directory) !== null && _a !== void 0 ? _a : core_backend_1.IModelHost.appWorkspace.containerDir, args.localFileName);
|
|
290
|
+
await core_backend_1.CloudSqlite.withWriteLock(args.user, container, async () => {
|
|
291
|
+
await performTransfer(container, "upload", args);
|
|
292
|
+
});
|
|
293
|
+
container.checkForChanges(); // so we can see newly imported WorkspaceDb
|
|
171
294
|
}
|
|
172
|
-
/**
|
|
173
|
-
async function
|
|
174
|
-
|
|
295
|
+
/** export a WorkspaceDb from a cloud WorkspaceContainer. */
|
|
296
|
+
async function exportWorkspaceDb(args) {
|
|
297
|
+
if (!(0, path_1.extname)(args.localFileName))
|
|
298
|
+
args.localFileName = `${args.localFileName}.${core_backend_1.ITwinWorkspaceDb.fileExt}`;
|
|
299
|
+
const dbParts = core_backend_1.ITwinWorkspaceContainer.parseDbFileName(args.dbName);
|
|
300
|
+
if (!dbParts.version)
|
|
301
|
+
throw new Error("exportDb requires a version");
|
|
302
|
+
await performTransfer(getCloudContainer(args), "download", args);
|
|
175
303
|
}
|
|
176
|
-
/** Delete a WorkspaceDb from a WorkspaceContainer. */
|
|
304
|
+
/** Delete a WorkspaceDb from a cloud WorkspaceContainer. */
|
|
177
305
|
async function deleteWorkspaceDb(args) {
|
|
178
|
-
|
|
179
|
-
|
|
306
|
+
const container = getCloudContainer(args);
|
|
307
|
+
await core_backend_1.CloudSqlite.withWriteLock(args.user, container, async () => {
|
|
308
|
+
return container.deleteDatabase(args.dbName);
|
|
309
|
+
});
|
|
310
|
+
showMessage(`deleted WorkspaceDb [${args.dbName}] from ${sayContainer(args)}`);
|
|
311
|
+
}
|
|
312
|
+
function sayContainer(args) {
|
|
313
|
+
return `container [${args.containerId}]`;
|
|
314
|
+
}
|
|
315
|
+
/** initialize (empty if it exists) a cloud WorkspaceContainer. */
|
|
316
|
+
async function initializeWorkspace(args) {
|
|
317
|
+
if (undefined === args.storageType || !args.accessName)
|
|
318
|
+
throw new Error("No cloud container supplied");
|
|
319
|
+
if (!args.noPrompt) {
|
|
320
|
+
const yesNo = await askQuestion(`Are you sure you want to initialize ${sayContainer(args)}"? [y/n]: `);
|
|
321
|
+
if (yesNo[0].toUpperCase() !== "Y")
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
const container = new core_backend_1.IModelHost.platform.CloudContainer(args);
|
|
325
|
+
container.initializeContainer({ checksumBlockNames: true });
|
|
326
|
+
showMessage(`container "${args.containerId} initialized`);
|
|
327
|
+
}
|
|
328
|
+
/** purge unused (garbage) blocks from a WorkspaceContainer. */
|
|
329
|
+
async function purgeWorkspace(args) {
|
|
330
|
+
const container = getCloudContainer(args);
|
|
331
|
+
const nGarbage = container.garbageBlocks;
|
|
332
|
+
await core_backend_1.CloudSqlite.withWriteLock(args.user, container, async () => container.cleanDeletedBlocks());
|
|
333
|
+
container.checkForChanges(); // re-read manifest to get current garbage count
|
|
334
|
+
showMessage(`purged ${sayContainer(args)}. ${nGarbage - container.garbageBlocks} garbage blocks cleaned`);
|
|
335
|
+
}
|
|
336
|
+
/** detach a WorkspaceContainer from the local cache. */
|
|
337
|
+
async function detachWorkspace(args) {
|
|
338
|
+
const container = getCloudContainer(args);
|
|
339
|
+
container.detach();
|
|
340
|
+
showMessage(`detached ${sayContainer(args)}.`);
|
|
341
|
+
}
|
|
342
|
+
/** Make a copy of a WorkspaceDb with a new name. */
|
|
343
|
+
async function copyWorkspaceDb(args) {
|
|
344
|
+
const container = getCloudContainer(args);
|
|
345
|
+
const oldName = core_backend_1.ITwinWorkspaceContainer.resolveCloudFileName(container, args);
|
|
346
|
+
const newVersion = core_backend_1.ITwinWorkspaceContainer.parseDbFileName(args.newDbName);
|
|
347
|
+
core_backend_1.ITwinWorkspaceContainer.validateDbName(newVersion.dbName);
|
|
348
|
+
const newName = core_backend_1.ITwinWorkspaceContainer.makeDbFileName(newVersion.dbName, core_backend_1.ITwinWorkspaceContainer.validateVersion(newVersion.version));
|
|
349
|
+
await core_backend_1.CloudSqlite.withWriteLock(args.user, container, async () => container.copyDatabase(oldName, newName));
|
|
350
|
+
showMessage(`copied WorkspaceDb [${oldName}] to [${newName}] in ${sayContainer(args)}`);
|
|
351
|
+
}
|
|
352
|
+
/** Make a copy of a WorkspaceDb with a new name. */
|
|
353
|
+
async function versionWorkspaceDb(args) {
|
|
354
|
+
fixVersionArg(args);
|
|
355
|
+
const container = getCloudContainer(args);
|
|
356
|
+
const result = await core_backend_1.ITwinWorkspaceContainer.makeNewVersion(container, args, args.versionType);
|
|
357
|
+
showMessage(`created new version: [${result.newName}] from [${result.oldName}] in ${sayContainer(args)}`);
|
|
358
|
+
}
|
|
359
|
+
/** pin a WorkspaceDb from a WorkspaceContainer. */
|
|
360
|
+
async function preFetchWorkspaceDb(args) {
|
|
361
|
+
fixVersionArg(args);
|
|
362
|
+
const container = getCloudContainer(args);
|
|
363
|
+
const timer = new core_bentley_1.StopWatch("prefetch", true);
|
|
364
|
+
const prefetch = new core_backend_1.IModelHost.platform.CloudPrefetch(container, args.dbFileName);
|
|
365
|
+
await prefetch.promise;
|
|
366
|
+
showMessage(`preFetched WorkspaceDb [${args.dbFileName}] in ${sayContainer(args)}, time=${timer.elapsedSeconds.toString()}`);
|
|
367
|
+
}
|
|
368
|
+
/** acquire the write lock for a WorkspaceContainer. */
|
|
369
|
+
async function acquireLock(args) {
|
|
370
|
+
const container = getCloudContainer(args);
|
|
371
|
+
if (container.hasWriteLock)
|
|
372
|
+
throw new Error(`write lock is already held for ${sayContainer(args)}`);
|
|
373
|
+
container.acquireWriteLock(args.user);
|
|
374
|
+
showMessage(`acquired lock for ${sayContainer(args)}`);
|
|
375
|
+
}
|
|
376
|
+
/** release the write lock for a WorkspaceContainer. */
|
|
377
|
+
async function releaseLock(args) {
|
|
378
|
+
const container = getCloudContainer(args);
|
|
379
|
+
if (!container.hasWriteLock)
|
|
380
|
+
throw new Error(`write lock is not held for ${sayContainer(args)}`);
|
|
381
|
+
container.releaseWriteLock();
|
|
382
|
+
showMessage(`released lock for ${sayContainer(args)}`);
|
|
383
|
+
}
|
|
384
|
+
/** clear the write lock for a WorkspaceContainer. */
|
|
385
|
+
async function clearWriteLock(args) {
|
|
386
|
+
const container = getCloudContainer(args);
|
|
387
|
+
container.clearWriteLock();
|
|
388
|
+
showMessage(`write lock cleared for ${sayContainer(args)}`);
|
|
389
|
+
}
|
|
390
|
+
/** query the list of WorkspaceDb in a WorkspaceContainer. */
|
|
391
|
+
async function queryWorkspaceDbs(args) {
|
|
392
|
+
const container = getCloudContainer(args);
|
|
393
|
+
const writeLockMsg = container.hasWriteLock ? ", writeLocked" : "";
|
|
394
|
+
const hasLocalMsg = container.hasLocalChanges ? ", has local changes" : "";
|
|
395
|
+
const nGarbage = container.garbageBlocks;
|
|
396
|
+
const garbageMsg = nGarbage ? `, ${nGarbage} garbage block${nGarbage > 1 ? "s" : ""}` : "";
|
|
397
|
+
const blockSize = container.blockSize;
|
|
398
|
+
showMessage(`WorkspaceDbs in ${sayContainer(args)}${writeLockMsg}${hasLocalMsg}${garbageMsg}`);
|
|
399
|
+
const dbs = container.queryDatabases(args.glob);
|
|
400
|
+
for (const dbName of dbs) {
|
|
401
|
+
const db = container.queryDatabase(dbName);
|
|
402
|
+
if (db) {
|
|
403
|
+
const dirty = db.dirtyBlocks ? `, ${db.dirtyBlocks} dirty` : "";
|
|
404
|
+
const editable = db.state === "copied" ? ", editable" : "";
|
|
405
|
+
showMessage(` "${dbName}", size=${friendlyFileSize(db.totalBlocks * blockSize)}, ${friendlyFileSize(db.localBlocks * blockSize)} downloaded (${(100 * db.localBlocks / db.totalBlocks).toFixed(0)}%)${editable}${dirty}`);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
180
408
|
}
|
|
181
409
|
/** Start `IModelHost`, then run a WorkspaceEditor command. Errors are logged to console. */
|
|
182
410
|
function runCommand(cmd) {
|
|
183
411
|
return async (args) => {
|
|
412
|
+
var _a;
|
|
413
|
+
if (inScript)
|
|
414
|
+
return cmd(args);
|
|
184
415
|
try {
|
|
185
416
|
const config = new core_backend_1.IModelHostConfiguration();
|
|
186
|
-
|
|
187
|
-
|
|
417
|
+
config.workspace = {
|
|
418
|
+
containerDir: args.directory,
|
|
419
|
+
cloudCacheProps: {
|
|
420
|
+
nRequests: args.nRequests,
|
|
421
|
+
curlDiagnostics: args.curlDiagnostics,
|
|
422
|
+
},
|
|
423
|
+
};
|
|
188
424
|
await core_backend_1.IModelHost.startup(config);
|
|
425
|
+
if (true === args.logging) {
|
|
426
|
+
core_bentley_1.Logger.initializeToConsole();
|
|
427
|
+
core_bentley_1.Logger.setLevel("CloudSqlite", core_bentley_1.LogLevel.Trace);
|
|
428
|
+
(_a = core_backend_1.IModelHost.appWorkspace.cloudCache) === null || _a === void 0 ? void 0 : _a.setLogMask(0xff);
|
|
429
|
+
logTimer = setInterval(() => flushLog(), 20); // logging from other threads is buffered. This causes it to appear every 1/4 second.
|
|
430
|
+
}
|
|
189
431
|
await cmd(args);
|
|
190
432
|
}
|
|
191
433
|
catch (e) {
|
|
192
|
-
|
|
434
|
+
if (typeof e.message === "string")
|
|
435
|
+
console.error(e.message);
|
|
436
|
+
else
|
|
437
|
+
console.log(core_bentley_1.BentleyError.getErrorMessage(e));
|
|
438
|
+
}
|
|
439
|
+
finally {
|
|
440
|
+
if (logTimer) {
|
|
441
|
+
flushLog();
|
|
442
|
+
clearInterval(logTimer);
|
|
443
|
+
}
|
|
444
|
+
await core_backend_1.IModelHost.shutdown();
|
|
193
445
|
}
|
|
194
446
|
};
|
|
195
447
|
}
|
|
448
|
+
const type = { alias: "t", describe: "Type of resource", choices: ["blob", "string", "file"], demandOption: true };
|
|
449
|
+
const addOrReplace = {
|
|
450
|
+
rscName: { alias: "n", describe: "Resource name for file", string: true },
|
|
451
|
+
root: { alias: "r", describe: "Root directory. Path parts after this will be saved in resource name", string: true },
|
|
452
|
+
type,
|
|
453
|
+
};
|
|
454
|
+
Yargs.usage("Edits or lists contents of a WorkspaceDb");
|
|
455
|
+
Yargs.wrap(Math.min(150, Yargs.terminalWidth()));
|
|
456
|
+
Yargs.env("WORKSPACE_EDITOR");
|
|
457
|
+
Yargs.config();
|
|
458
|
+
Yargs.help();
|
|
459
|
+
Yargs.version("V2.0");
|
|
460
|
+
Yargs.options({
|
|
461
|
+
directory: { alias: "d", describe: "Directory to use for WorkspaceContainers", string: true },
|
|
462
|
+
nRequests: { describe: "Number of simultaneous http requests for cloud operations", number: true, hidden: true },
|
|
463
|
+
containerId: { alias: "c", describe: "ContainerId for WorkspaceDb", string: true, demandOption: true },
|
|
464
|
+
user: { describe: "String shown in cloud container locked message", string: true, default: "workspace-editor" },
|
|
465
|
+
accessName: { alias: "a", describe: "Cloud storage account name for container", string: true },
|
|
466
|
+
accessToken: { describe: "Token that grants access to the container (either SAS or account key)", string: true, default: "" },
|
|
467
|
+
storageType: { describe: "Cloud storage module type", string: true, default: "azure?sas=1" },
|
|
468
|
+
logging: { describe: "enable log messages", boolean: true, default: false, hidden: true },
|
|
469
|
+
prefetch: { boolean: true, default: false, hidden: true },
|
|
470
|
+
curlDiagnostics: { boolean: true, default: false, hidden: true },
|
|
471
|
+
});
|
|
472
|
+
Yargs.command({
|
|
473
|
+
command: "add <dbName> <files>",
|
|
474
|
+
describe: "add files into a WorkspaceDb",
|
|
475
|
+
builder: addOrReplace,
|
|
476
|
+
handler: runCommand(addResource),
|
|
477
|
+
});
|
|
478
|
+
Yargs.command({
|
|
479
|
+
command: "replace <dbName> <files>",
|
|
480
|
+
describe: "replace files in a WorkspaceDb",
|
|
481
|
+
builder: addOrReplace,
|
|
482
|
+
handler: runCommand(replaceResource),
|
|
483
|
+
});
|
|
484
|
+
Yargs.command({
|
|
485
|
+
command: "remove <dbName> <rscName>",
|
|
486
|
+
describe: "remove a resource from a WorkspaceDb",
|
|
487
|
+
builder: { type },
|
|
488
|
+
handler: runCommand(removeResource),
|
|
489
|
+
});
|
|
490
|
+
Yargs.command({
|
|
491
|
+
command: "extract <dbName> <rscName> <fileName>",
|
|
492
|
+
describe: "extract a resource from a WorkspaceDb into a local file",
|
|
493
|
+
builder: { type },
|
|
494
|
+
handler: runCommand(extractResource),
|
|
495
|
+
});
|
|
496
|
+
Yargs.command({
|
|
497
|
+
command: "listDb <dbName>",
|
|
498
|
+
describe: "list the contents of a WorkspaceDb",
|
|
499
|
+
builder: {
|
|
500
|
+
strings: { alias: "s", describe: "list string resources", boolean: true, default: false },
|
|
501
|
+
blobs: { alias: "b", describe: "list blob resources", boolean: true, default: false },
|
|
502
|
+
files: { alias: "f", describe: "list file resources", boolean: true, default: false },
|
|
503
|
+
},
|
|
504
|
+
handler: runCommand(listWorkspaceDb),
|
|
505
|
+
});
|
|
506
|
+
Yargs.command({
|
|
507
|
+
command: "deleteDb <dbName>",
|
|
508
|
+
describe: "delete a WorkspaceDb from a cloud container",
|
|
509
|
+
handler: runCommand(deleteWorkspaceDb),
|
|
510
|
+
});
|
|
511
|
+
Yargs.command({
|
|
512
|
+
command: "createDb <dbName>",
|
|
513
|
+
describe: "create a new WorkspaceDb",
|
|
514
|
+
handler: runCommand(createWorkspaceDb),
|
|
515
|
+
});
|
|
516
|
+
Yargs.command({
|
|
517
|
+
command: "copyDb <dbName> <newDbName>",
|
|
518
|
+
describe: "make a copy of a WorkspaceDb in a cloud container with a new name",
|
|
519
|
+
handler: runCommand(copyWorkspaceDb),
|
|
520
|
+
});
|
|
521
|
+
Yargs.command({
|
|
522
|
+
command: "versionDb <dbName>",
|
|
523
|
+
describe: "make a new version of a WorkspaceDb",
|
|
524
|
+
builder: {
|
|
525
|
+
versionType: { describe: "the type of version to create", default: "patch", string: true, choices: ["major", "minor", "patch"] },
|
|
526
|
+
},
|
|
527
|
+
handler: runCommand(versionWorkspaceDb),
|
|
528
|
+
});
|
|
529
|
+
Yargs.command({
|
|
530
|
+
command: "prefetchDb <dbName>",
|
|
531
|
+
describe: false,
|
|
532
|
+
handler: runCommand(preFetchWorkspaceDb),
|
|
533
|
+
});
|
|
534
|
+
Yargs.command({
|
|
535
|
+
command: "vacuumDb <dbName>",
|
|
536
|
+
describe: "vacuum a WorkspaceDb",
|
|
537
|
+
handler: runCommand(vacuumWorkspaceDb),
|
|
538
|
+
});
|
|
539
|
+
Yargs.command({
|
|
540
|
+
command: "importDb <dbName> <localFileName>",
|
|
541
|
+
describe: "import a WorkspaceDb into a cloud container",
|
|
542
|
+
builder: {
|
|
543
|
+
noVacuum: { describe: "Don't vacuum source Db before importing", boolean: true },
|
|
544
|
+
},
|
|
545
|
+
handler: runCommand(importWorkspaceDb),
|
|
546
|
+
});
|
|
547
|
+
Yargs.command({
|
|
548
|
+
command: "exportDb <dbName> <localFileName>",
|
|
549
|
+
describe: "export a WorkspaceDb from a cloud container to a local file",
|
|
550
|
+
handler: runCommand(exportWorkspaceDb),
|
|
551
|
+
});
|
|
552
|
+
Yargs.command({
|
|
553
|
+
command: "queryDbs [glob]",
|
|
554
|
+
describe: "query the list of WorkspaceDbs in a cloud container",
|
|
555
|
+
handler: runCommand(queryWorkspaceDbs),
|
|
556
|
+
});
|
|
557
|
+
Yargs.command({
|
|
558
|
+
command: "acquireLock",
|
|
559
|
+
describe: "acquire the write lock for a cloud container",
|
|
560
|
+
handler: runCommand(acquireLock),
|
|
561
|
+
});
|
|
562
|
+
Yargs.command({
|
|
563
|
+
command: "releaseLock",
|
|
564
|
+
describe: "release the write lock for a cloud container",
|
|
565
|
+
handler: runCommand(releaseLock),
|
|
566
|
+
});
|
|
567
|
+
Yargs.command({
|
|
568
|
+
command: "clearWriteLock",
|
|
569
|
+
describe: "clear the write lock for a cloud container. Note: this can be dangerous!",
|
|
570
|
+
handler: runCommand(clearWriteLock),
|
|
571
|
+
});
|
|
572
|
+
Yargs.command({
|
|
573
|
+
command: "purgeWorkspace",
|
|
574
|
+
describe: "purge deleted blocks from a WorkspaceContainer",
|
|
575
|
+
handler: runCommand(purgeWorkspace),
|
|
576
|
+
});
|
|
577
|
+
Yargs.command({
|
|
578
|
+
command: "detachWorkspace",
|
|
579
|
+
describe: false,
|
|
580
|
+
handler: runCommand(detachWorkspace),
|
|
581
|
+
});
|
|
582
|
+
Yargs.command({
|
|
583
|
+
command: "initializeWorkspace",
|
|
584
|
+
describe: "initialize a WorkspaceContainer (empties if already initialized)",
|
|
585
|
+
builder: {
|
|
586
|
+
noPrompt: { describe: "Skip prompt", boolean: true, default: false },
|
|
587
|
+
},
|
|
588
|
+
handler: runCommand(initializeWorkspace),
|
|
589
|
+
});
|
|
590
|
+
/** execute an "@" script - a list of WorkspaceEditor commands */
|
|
591
|
+
async function runScript(arg) {
|
|
592
|
+
inScript = true;
|
|
593
|
+
const val = fs.readFileSync(arg.scriptName, "utf-8");
|
|
594
|
+
const lines = val.split(/\r?\n/);
|
|
595
|
+
let i = 0;
|
|
596
|
+
for (let line of lines) {
|
|
597
|
+
i++;
|
|
598
|
+
line = line.split("#")[0].trim(); // ignore leading/trailing whitespace and comments (anything after a "#")
|
|
599
|
+
if (line.length === 0)
|
|
600
|
+
continue; // blank line
|
|
601
|
+
await Yargs.parseAsync(line, {}, (err, _argv, _output) => {
|
|
602
|
+
if (err) {
|
|
603
|
+
console.error(`${arg.scriptName}:${i} [${line}] : ${core_bentley_1.BentleyError.getErrorMessage(err)}`);
|
|
604
|
+
process.exit(1);
|
|
605
|
+
}
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
}
|
|
196
609
|
/** Parse and execute WorkspaceEditor commands */
|
|
197
610
|
async function main() {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
Yargs.
|
|
207
|
-
Yargs.options({
|
|
208
|
-
directory: { alias: "d", describe: "directory to use for WorkspaceContainers", string: true },
|
|
209
|
-
containerId: { alias: "c", describe: "WorkspaceContainerId for WorkspaceDb", string: true, required: true },
|
|
210
|
-
sasToken: { alias: "s", describe: "shared access signature token", string: true, default: "" },
|
|
211
|
-
accountName: { alias: "a", describe: "cloud storage account name for container", string: true, default: "" },
|
|
212
|
-
storageType: { describe: "storage module type", string: true, default: "azure?sas=1" },
|
|
213
|
-
});
|
|
214
|
-
Yargs.command({
|
|
215
|
-
command: "create <dbName>",
|
|
216
|
-
describe: "create a new WorkspaceDb",
|
|
217
|
-
handler: runCommand(createWorkspaceDb),
|
|
218
|
-
});
|
|
219
|
-
Yargs.command({
|
|
220
|
-
command: "list <dbName>",
|
|
221
|
-
describe: "list the contents of a WorkspaceDb",
|
|
222
|
-
builder: {
|
|
223
|
-
strings: { alias: "s", describe: "list string resources", boolean: true, default: false },
|
|
224
|
-
blobs: { alias: "b", describe: "list blob resources", boolean: true, default: false },
|
|
225
|
-
files: { alias: "f", describe: "list file resources", boolean: true, default: false },
|
|
226
|
-
},
|
|
227
|
-
handler: runCommand(listWorkspaceDb),
|
|
228
|
-
});
|
|
229
|
-
Yargs.command({
|
|
230
|
-
command: "add <dbName> <files>",
|
|
231
|
-
describe: "add or update files into a WorkspaceDb",
|
|
232
|
-
builder: {
|
|
233
|
-
name: { alias: "n", describe: "resource name for file", string: true },
|
|
234
|
-
root: { alias: "r", describe: "root directory. Path parts after this will be saved in resource name", string: true },
|
|
235
|
-
update,
|
|
236
|
-
type,
|
|
237
|
-
},
|
|
238
|
-
handler: runCommand(addResource),
|
|
239
|
-
});
|
|
240
|
-
Yargs.command({
|
|
241
|
-
command: "extract <dbName> <rscName> <fileName>",
|
|
242
|
-
describe: "extract a resource from a WorkspaceDb into a local file",
|
|
243
|
-
builder: { type },
|
|
244
|
-
handler: runCommand(extractResource),
|
|
245
|
-
});
|
|
246
|
-
Yargs.command({
|
|
247
|
-
command: "remove <dbName> <rscName>",
|
|
248
|
-
describe: "remove a resource from a WorkspaceDb",
|
|
249
|
-
builder: { type },
|
|
250
|
-
handler: runCommand(removeResource),
|
|
251
|
-
});
|
|
252
|
-
Yargs.command({
|
|
253
|
-
command: "upload <dbName>",
|
|
254
|
-
describe: "upload a WorkspaceDb to cloud storage",
|
|
255
|
-
builder: {
|
|
256
|
-
initialize: { alias: "i", describe: "initialize container", boolean: true, default: false },
|
|
257
|
-
localFile: { alias: "l", describe: "name of source local file", string: true, required: false },
|
|
258
|
-
},
|
|
259
|
-
handler: runCommand(uploadWorkspaceDb),
|
|
260
|
-
});
|
|
261
|
-
Yargs.command({
|
|
262
|
-
command: "download <dbName>",
|
|
263
|
-
describe: "download a WorkspaceDb from cloud storage to local file",
|
|
264
|
-
builder: {
|
|
265
|
-
localFile: { alias: "l", describe: "name of target local file", string: true, required: false },
|
|
266
|
-
},
|
|
267
|
-
handler: runCommand(downloadWorkspaceDb),
|
|
268
|
-
});
|
|
269
|
-
Yargs.command({
|
|
270
|
-
command: "delete <dbName>",
|
|
271
|
-
describe: "delete a WorkspaceDb from cloud storage",
|
|
272
|
-
handler: runCommand(deleteWorkspaceDb),
|
|
273
|
-
});
|
|
274
|
-
Yargs.command({
|
|
275
|
-
command: "vacuum <dbName>",
|
|
276
|
-
describe: "vacuum a WorkspaceDb",
|
|
277
|
-
handler: runCommand(vacuumWorkspaceDb),
|
|
278
|
-
});
|
|
611
|
+
var _a;
|
|
612
|
+
if (process.argv.length > 1 && ((_a = process.argv[2]) === null || _a === void 0 ? void 0 : _a[0]) === "@") {
|
|
613
|
+
const parsed = Yargs.parseSync(process.argv.slice(3));
|
|
614
|
+
if (parsed.config)
|
|
615
|
+
process.env.WORKSPACE_EDITOR_CONFIG = parsed.config;
|
|
616
|
+
await runCommand(runScript)({ ...parsed, scriptName: process.argv[2].substring(1) });
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
Yargs.strictCommands();
|
|
279
620
|
Yargs.demandCommand();
|
|
280
|
-
Yargs.
|
|
621
|
+
await Yargs.parseAsync();
|
|
281
622
|
}
|
|
282
623
|
void main();
|
|
283
624
|
//# sourceMappingURL=WorkspaceEditor.js.map
|