@itwin/workspace-editor 3.3.0-dev.5 → 3.3.0-dev.52

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.
@@ -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
- const wsFile = new core_backend_1.EditableWorkspaceDb(args.dbName, core_backend_1.IModelHost.appWorkspace.getContainer(args));
17
- wsFile.create();
18
- console.log(`created WorkspaceDb ${wsFile.sqliteDb.nativeDb.getFilePath()}`);
19
- wsFile.close();
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
- console.log(`WorkspaceDb [${ws.sqliteDb.nativeDb.getFilePath()}]`);
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
- processWorkspace(args, new core_backend_1.EditableWorkspaceDb(args.dbName, core_backend_1.IModelHost.appWorkspace.getContainer(args)), fn);
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
- processWorkspace(args, new core_backend_1.ITwinWorkspaceDb(args.dbName, core_backend_1.IModelHost.appWorkspace.getContainer(args)), fn);
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
- console.log(" strings:");
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
- console.log(` name=[${stmt.getValueString(0)}], size=${stmt.getValueString(1).length}`);
120
+ nameAndSize(stmt);
50
121
  });
51
122
  }
52
123
  if (args.blobs) {
53
- console.log(" blobs:");
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
- console.log(` name=[${stmt.getValueString(0)}], size=${stmt.getValueBlob(1).length}`);
127
+ nameAndSize(stmt);
57
128
  });
58
129
  }
59
130
  if (args.files) {
60
- console.log(" files:");
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
- console.log(` name=[${stmt.getValueString(0)}], size=${info.size}, ext="${info.fileExt}", date=${date.toString()}`);
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 or Update files into a WorkspaceDb. */
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[args.update ? "updateString" : "addString"](name, val);
171
+ wsFile.addString(name, val);
88
172
  }
89
173
  else if (args.type === "blob") {
90
174
  const val = fs.readFileSync(file);
91
- wsFile[args.update ? "updateBlob" : "addBlob"](name, val);
175
+ wsFile.addBlob(name, val);
92
176
  }
93
177
  else {
94
- wsFile[args.update ? "updateFile" : "addFile"](name, file);
178
+ wsFile.addFile(name, file);
95
179
  }
96
- console.log(` ${args.update ? "updated" : "added"} "${file}" as ${args.type} resource [${name}]`);
180
+ showMessage(` added "${file}" as ${args.type} resource [${name}]`);
97
181
  }
98
182
  catch (e) {
99
- console.error(e.message);
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
- console.log(` ${args.type} resource [${args.rscName}] extracted to "${args.fileName}"`);
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
- console.log(` removed ${args.type} resource [${args.rscName}]`);
247
+ showMessage(` removed ${args.type} resource [${args.rscName}]`);
134
248
  });
135
249
  }
136
- function getDbFileName(args) {
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 localFile = getDbFileName(args);
142
- core_backend_1.IModelHost.platform.DgnDb.vacuum(localFile);
143
- console.log(`${localFile} vacuumed`);
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
- var _a;
148
- const localFile = (_a = args.localFile) !== null && _a !== void 0 ? _a : getDbFileName(args);
149
- const info = `${direction} ${localFile}, containerId=${args.containerId}, WorkspaceDbName=${args.dbName} : `;
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
- await core_backend_1.CloudSqlite.transferDb(direction, { ...args, localFile, onProgress });
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
- /** Upload a WorkspaceDb to a WorkspaceContainer. */
167
- async function uploadWorkspaceDb(args) {
168
- if (args.initialize)
169
- await core_backend_1.CloudSqlite.initializeContainer(args);
170
- return performTransfer("upload", args);
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
- /** Download a WorkspaceDb from a WorkspaceContainer. */
173
- async function downloadWorkspaceDb(args) {
174
- return performTransfer("download", args);
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
- await core_backend_1.CloudSqlite.deleteDb(args);
179
- console.log(`deleted WorkspaceDb [${args.dbName}] from containerId: ${args.containerId}`);
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
- if (args.directory)
187
- config.workspace = { containerDir: args.directory };
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
- console.error(e.message);
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
- const type = { alias: "t", describe: "the type of resource", choices: ["blob", "string", "file"], required: true };
199
- const update = { alias: "u", describe: "update (i.e. replace) rather than add the files", boolean: true, default: false };
200
- Yargs.usage("Edits or lists contents of a WorkspaceDb");
201
- Yargs.wrap(Math.min(130, Yargs.terminalWidth()));
202
- Yargs.strict();
203
- Yargs.config();
204
- Yargs.default("config", "workspaceEditor.json");
205
- Yargs.help();
206
- Yargs.version("V2.0");
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.argv;
621
+ await Yargs.parseAsync();
281
622
  }
282
623
  void main();
283
624
  //# sourceMappingURL=WorkspaceEditor.js.map