@itwin/core-backend 3.0.0-dev.103 → 3.0.0-dev.107

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.
@@ -0,0 +1,255 @@
1
+ /** @packageDocumentation
2
+ * @module Workspace
3
+ */
4
+ import { AccessToken, BeEvent } from "@itwin/core-bentley";
5
+ import { LocalDirName, LocalFileName } from "@itwin/core-common";
6
+ import { IModelDb } from "../IModelDb";
7
+ import { SQLiteDb } from "../SQLiteDb";
8
+ import { Settings, SettingsPriority } from "./Settings";
9
+ /**
10
+ * The name of a workspace container. This is the user-supplied name of a container, used to specify its *purpose* within a workspace.
11
+ * WorkspaceNames can be "aliased" by `WorkspaceSetting.ContainerAlias` settings so that "resolved" [[WorkspaceContainerId]] that supplies
12
+ * the actual container for a WorkspaceContainerName may vary. Also note that more than one WorkspaceContainerName may resolve to the same
13
+ * WorkspaceContainerId, if multiple purposes are served by the same container.
14
+ * @note there are no constraints on the contents or length of `WorkspaceContainerName`s, although short descriptive names are recommended.
15
+ * However, when no alias exists in WorkspaceSetting.ContainerAlias for a WorkspaceContainerName, then the WorkspaceContainerName becomes
16
+ * the WorkspaceContainerId, and the constraints on WorkspaceContainerId apply.
17
+ * @beta
18
+ */
19
+ export declare type WorkspaceContainerName = string;
20
+ /**
21
+ * The unique identifier of a WorkspaceContainer. This becomes the base name for the local file holding the WorkspaceContainer, and is also the
22
+ * name of its cloud-storage-container. `WorkspaceContainerName` are resolved to WorkspaceContainerId through `WorkspaceSetting.ContainerAlias` settings,
23
+ * so users may not recognize the actual WorkspaceContainerId supplying resources for a WorkspaceContainerName.
24
+ *
25
+ * `WorkspaceContainerId`s may not:
26
+ * - be blank or start or end with a space
27
+ * - be longer than 255 characters
28
+ * - contain any characters with Unicode values less than 0x20
29
+ * - contain characters reserved for filename, device, wildcard, or url syntax (e.g. "\.<>:"/\\"`'|?*")
30
+ * @beta
31
+ */
32
+ export declare type WorkspaceContainerId = string;
33
+ /**
34
+ * The name for identifying WorkspaceResources in a [[WorkspaceContainer]].
35
+ * * `WorkspaceResourceName`s may not:
36
+ * - be blank or start or end with a space
37
+ * - be longer than 1024 characters
38
+ * @note a single WorkspaceContainer may hold WorkspaceResources of type 'blob', 'string' and 'file', all with the same WorkspaceResourceName.
39
+ * @beta
40
+ */
41
+ export declare type WorkspaceResourceName = string;
42
+ /**
43
+ * Properties that specify a WorkspaceContainer. This can either be a WorkspaceContainerName or an
44
+ * object with a member named `id` that holds a WorkspaceContainerId. If WorkspaceContainerId is supplied,
45
+ * it is used directly. Otherwise the name must be resolved via [[Workspace.resolveContainerId]].
46
+ * @beta
47
+ */
48
+ export declare type WorkspaceContainerProps = WorkspaceContainerName | {
49
+ id: WorkspaceContainerId;
50
+ };
51
+ /** Properties that specify a WorkspaceResource within a WorkspaceContainer.
52
+ * @beta
53
+ */
54
+ export interface WorkspaceResourceProps {
55
+ /** the properties of the WorkspaceContainer holding the resource. */
56
+ container: WorkspaceContainerProps;
57
+ /** the name of the resource within [[container]] */
58
+ rscName: WorkspaceResourceName;
59
+ }
60
+ /**
61
+ * A container of workspace resources. `WorkspaceContainer`s may just be local [[WorkspaceFile]]s, or they may be stored and
62
+ * synchronized with cloud blob-store containers. WorkspaceContainers hold WorkspaceResources, each identified by a [[WorkspaceResourceName]].
63
+ * Resources of type `string` and `blob` may be loaded directly from the `WorkspaceContainer`. Resources of type `file` are
64
+ * copied from the container into a temporary local file so they can be accessed directly.
65
+ * @beta
66
+ */
67
+ export interface WorkspaceContainer {
68
+ /** The WorkspaceContainerId of this container. */
69
+ readonly containerId: WorkspaceContainerId;
70
+ /** The Workspace that opened this WorkspaceContainer */
71
+ readonly workspace: Workspace;
72
+ /** If present, IModelDb that owns this [[WorkspaceContainer]]. The lifetime of this container is paired with the iModelDb. */
73
+ readonly iModelOwner?: IModelDb;
74
+ /** the directory for extracting file resources. */
75
+ readonly containerFilesDir: LocalDirName;
76
+ /** event raised when the container is closed. */
77
+ readonly onContainerClosed: BeEvent<() => void>;
78
+ /** Get a string resource from this container, if present. */
79
+ getString(rscName: WorkspaceResourceName): string | undefined;
80
+ /** Get a blob resource from this container, if present. */
81
+ getBlob(rscName: WorkspaceResourceName): Uint8Array | undefined;
82
+ /** Extract a local copy of a file resource from this container, if present.
83
+ * @param rscName The name of the file resource in the WorkspaceContainer
84
+ * @param targetFileName optional name for extracted file. Some applications require files in specific locations or filenames. If
85
+ * you know the full path to use for the extracted file, you can supply it. Generally, it is best to *not* supply the filename and
86
+ * keep the extracted files in the [[containerFilesDir]].
87
+ * @returns the full path to a file on the local filesystem.
88
+ * @note The file is copied from the container into the local filesystem so it may be accessed directly. This happens only
89
+ * as necessary, if the local file doesn't exist, or if it is out-of-date because it was updated in the container.
90
+ * For this reason, you should not save the local file name, and instead call this method every time you access it, so its
91
+ * content is always holds the correct version.
92
+ * @note The filename will be a hash value, not the resource name.
93
+ * @note Workspace resource files are set readonly as they are copied from the container.
94
+ * To edit them, you must first copy them to another location.
95
+ */
96
+ getFile(rscName: WorkspaceResourceName, targetFileName?: LocalFileName): LocalFileName | undefined;
97
+ }
98
+ /**
99
+ * Options supplied when opening a WorkspaceContainer.
100
+ * @beta
101
+ */
102
+ export interface WorkspaceContainerOpts {
103
+ /** If present, the container will be closed and removed when the iModel is closed. */
104
+ forIModel?: IModelDb;
105
+ }
106
+ /**
107
+ * Options for constructing a [[Workspace]].
108
+ * @beta
109
+ */
110
+ export interface WorkspaceOpts {
111
+ /** The local directory for the WorkspaceContainer files. The [[Workspace]] will (only) look in this directory
112
+ * for files named `${this.containerId}.itwin-workspace-container`.
113
+ * @note if not supplied, defaults to `iTwin/Workspace` in the user-local folder.
114
+ */
115
+ containerDir?: LocalDirName;
116
+ /** A local directory to store temporary files extracted for file-resources.
117
+ * @note if not supplied, defaults to `a folder named "Files" inside [[containerDir]]
118
+ */
119
+ filesDir?: LocalDirName;
120
+ }
121
+ /**
122
+ * Settings and resources that customize an application for the current session.
123
+ * See [Workspaces]($docs/learning/backend/Workspace)
124
+ * @beta
125
+ */
126
+ export interface Workspace {
127
+ /** The local directory for the WorkspaceContainer files with the name `${containerId}.itwin-workspace-container`. */
128
+ readonly containerDir: LocalDirName;
129
+ /** the local directory where this Workspace will store temporary files extracted for file-resources. */
130
+ readonly filesDir: LocalDirName;
131
+ /** The [[Settings]] for this Workspace */
132
+ readonly settings: Settings;
133
+ /**
134
+ * Resolve a WorkspaceContainerProps to a WorkspaceContainerId. If props is an object with an `id` member, that value is returned unchanged.
135
+ * If it is a string, then the highest priority [[WorkspaceSetting.ContainerAlias]] setting with an entry for the WorkspaceContainerName
136
+ * is used. If no WorkspaceSetting.ContainerAlias entry for the WorkspaceContainerName can be found, the name is returned as the id.
137
+ */
138
+ resolveContainerId(props: WorkspaceContainerProps): WorkspaceContainerId;
139
+ /**
140
+ * Get an open [[WorkspaceContainer]]. If the container is present but not open, it is opened first.
141
+ * If it is not present or not up-to-date, it is downloaded first.
142
+ * @returns a Promise that is resolved when the container is local, opened, and available for access.
143
+ */
144
+ getContainer(props: WorkspaceContainerProps, opts?: WorkspaceContainerOpts): Promise<WorkspaceContainer>;
145
+ /** Load a WorkspaceResource of type string, parse it, and add it to the current Settings for this Workspace.
146
+ * @note settingsRsc must specify a resource holding a stringified JSON representation of a [[SettingDictionary]]
147
+ * @returns a Promise that is resolved when the settings resource has been loaded.
148
+ */
149
+ loadSettingsDictionary(settingRsc: WorkspaceResourceProps, priority: SettingsPriority): Promise<void>;
150
+ /** Close and remove a currently opened [[WorkspaceContainer]] from this Workspace. */
151
+ dropContainer(container: WorkspaceContainer): void;
152
+ /** Close this Workspace. All currently opened WorkspaceContainers are dropped. */
153
+ close(): void;
154
+ }
155
+ /** @internal */
156
+ export declare class ITwinWorkspace implements Workspace {
157
+ private _containers;
158
+ readonly filesDir: LocalDirName;
159
+ readonly containerDir: LocalDirName;
160
+ readonly settings: Settings;
161
+ constructor(opts?: WorkspaceOpts);
162
+ getContainer(props: WorkspaceContainerProps, opts?: WorkspaceContainerOpts): Promise<WorkspaceContainer>;
163
+ loadSettingsDictionary(settingRsc: WorkspaceResourceProps, priority: SettingsPriority): Promise<void>;
164
+ close(): void;
165
+ dropContainer(toDrop: WorkspaceContainer): void;
166
+ resolveContainerId(props: WorkspaceContainerProps): WorkspaceContainerId;
167
+ }
168
+ /**
169
+ * A local file holding a WorkspaceContainer.
170
+ * @beta
171
+ */
172
+ export declare class WorkspaceFile implements WorkspaceContainer {
173
+ protected readonly db: SQLiteDb;
174
+ readonly workspace: Workspace;
175
+ readonly containerId: WorkspaceContainerId;
176
+ readonly localDbName: LocalDirName;
177
+ readonly iModelOwner?: IModelDb;
178
+ readonly onContainerClosed: BeEvent<() => void>;
179
+ get containerFilesDir(): string;
180
+ get isOpen(): boolean;
181
+ protected queryFileResource(rscName: WorkspaceResourceName): {
182
+ localFileName: string;
183
+ info: import("@bentley/imodeljs-native").IModelJsNative.EmbedFileQuery;
184
+ } | undefined;
185
+ protected static noLeadingOrTrailingSpaces(name: string, msg: string): void;
186
+ private static validateContainerId;
187
+ constructor(containerId: WorkspaceContainerId, workspace: Workspace, opts?: WorkspaceContainerOpts);
188
+ attach(_token: AccessToken): Promise<void>;
189
+ download(): Promise<void>;
190
+ purgeContainerFiles(): void;
191
+ open(): void;
192
+ close(): void;
193
+ getString(rscName: WorkspaceResourceName): string | undefined;
194
+ getBlob(rscName: WorkspaceResourceName): Uint8Array | undefined;
195
+ getFile(rscName: WorkspaceResourceName, targetFileName?: LocalFileName): LocalFileName | undefined;
196
+ }
197
+ /**
198
+ * An editable [[WorkspaceFile]]. This is used by administrators for creating and modifying `WorkspaceContainer`s.
199
+ * For cloud-backed containers, the write token must be obtained before this class may be used. Only one user at at time
200
+ * may be editing.
201
+ * @beta
202
+ */
203
+ export declare class EditableWorkspaceFile extends WorkspaceFile {
204
+ private static validateResourceName;
205
+ private validateResourceSize;
206
+ upload(): Promise<void>;
207
+ lockContainer(): Promise<void>;
208
+ private getFileModifiedTime;
209
+ private performWriteSql;
210
+ /** Create a new, empty, EditableWorkspaceFile for importing Workspace resources. */
211
+ create(): void;
212
+ /** Add a new string resource to this WorkspaceFile.
213
+ * @param rscName The name of the string resource.
214
+ * @param val The string to save.
215
+ */
216
+ addString(rscName: WorkspaceResourceName, val: string): void;
217
+ /** Update an existing string resource with a new value.
218
+ * @param rscName The name of the string resource.
219
+ * @param val The new value.
220
+ * @throws if rscName does not exist
221
+ */
222
+ updateString(rscName: WorkspaceResourceName, val: string): void;
223
+ /** Remove a string resource. */
224
+ removeString(rscName: WorkspaceResourceName): void;
225
+ /** Add a new blob resource to this WorkspaceFile.
226
+ * @param rscName The name of the blob resource.
227
+ * @param val The blob to save.
228
+ */
229
+ addBlob(rscName: WorkspaceResourceName, val: Uint8Array): void;
230
+ /** Update an existing blob resource with a new value.
231
+ * @param rscName The name of the blob resource.
232
+ * @param val The new value.
233
+ * @throws if rscName does not exist
234
+ */
235
+ updateBlob(rscName: WorkspaceResourceName, val: Uint8Array): void;
236
+ /** Remove a blob resource. */
237
+ removeBlob(rscName: WorkspaceResourceName): void;
238
+ /** Copy the contents of an existing local file into this WorkspaceFile as a file resource.
239
+ * @param rscName The name of the file resource.
240
+ * @param localFileName The name of a local file to be read.
241
+ * @param fileExt The extension (do not include the leading ".") to be appended to the generated fileName
242
+ * when this file is extracted from the WorkspaceContainer. By default the characters after the last "." in `localFileName`
243
+ * are used. Pass this argument to override that.
244
+ */
245
+ addFile(rscName: WorkspaceResourceName, localFileName: LocalFileName, fileExt?: string): void;
246
+ /** Replace an existing file resource with the contents of a local file.
247
+ * @param rscName The name of the file resource.
248
+ * @param localFileName The name of a local file to be read.
249
+ * @throws if rscName does not exist
250
+ */
251
+ updateFile(rscName: WorkspaceResourceName, localFileName: LocalFileName): void;
252
+ /** Remove a file resource. */
253
+ removeFile(rscName: WorkspaceResourceName): void;
254
+ }
255
+ //# sourceMappingURL=Workspace.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Workspace.d.ts","sourceRoot":"","sources":["../../../src/workspace/Workspace.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,291 @@
1
+ "use strict";
2
+ /*---------------------------------------------------------------------------------------------
3
+ * Copyright (c) Bentley Systems, Incorporated. All rights reserved.
4
+ * See LICENSE.md in the project root for license terms and full copyright notice.
5
+ *--------------------------------------------------------------------------------------------*/
6
+ /** @packageDocumentation
7
+ * @module Workspace
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.EditableWorkspaceFile = exports.WorkspaceFile = exports.ITwinWorkspace = void 0;
11
+ const crypto_1 = require("crypto");
12
+ const fs = require("fs-extra");
13
+ const path_1 = require("path");
14
+ const core_bentley_1 = require("@itwin/core-bentley");
15
+ const core_common_1 = require("@itwin/core-common");
16
+ const IModelJsFs_1 = require("../IModelJsFs");
17
+ const SQLiteDb_1 = require("../SQLiteDb");
18
+ const Settings_1 = require("./Settings");
19
+ const imodeljs_native_1 = require("@bentley/imodeljs-native");
20
+ /** The names of Settings used by Workspace
21
+ * @beta
22
+ */
23
+ var WorkspaceSetting;
24
+ (function (WorkspaceSetting) {
25
+ WorkspaceSetting["ContainerAlias"] = "workspace/container/alias";
26
+ })(WorkspaceSetting || (WorkspaceSetting = {}));
27
+ const containerFileExt = "itwin-workspace-container";
28
+ /** @internal */
29
+ class ITwinWorkspace {
30
+ constructor(opts) {
31
+ var _a, _b;
32
+ this._containers = new Map();
33
+ this.settings = new Settings_1.ITwinSettings();
34
+ this.containerDir = (_a = opts === null || opts === void 0 ? void 0 : opts.containerDir) !== null && _a !== void 0 ? _a : (0, path_1.join)(imodeljs_native_1.NativeLibrary.defaultLocalDir, "iTwin", "Workspace");
35
+ this.filesDir = (_b = opts === null || opts === void 0 ? void 0 : opts.filesDir) !== null && _b !== void 0 ? _b : (0, path_1.join)(this.containerDir, "Files");
36
+ }
37
+ async getContainer(props, opts) {
38
+ const id = this.resolveContainerId(props);
39
+ if (undefined === id)
40
+ throw new Error(`can't resolve container name [${props}]`);
41
+ let container = this._containers.get(id);
42
+ if (container)
43
+ return container;
44
+ container = new WorkspaceFile(id, this, opts);
45
+ container.open();
46
+ this._containers.set(id, container);
47
+ if (opts === null || opts === void 0 ? void 0 : opts.forIModel)
48
+ opts.forIModel.onBeforeClose.addOnce(() => this.dropContainer(container));
49
+ return container;
50
+ }
51
+ async loadSettingsDictionary(settingRsc, priority) {
52
+ const container = await this.getContainer(settingRsc.container);
53
+ const setting = container.getString(settingRsc.rscName);
54
+ if (undefined === setting)
55
+ throw new Error(`could not load setting resource ${settingRsc.rscName}`);
56
+ this.settings.addJson(`${container.containerId}/${settingRsc.rscName}`, priority, setting);
57
+ }
58
+ close() {
59
+ for (const [_id, container] of this._containers)
60
+ container.close();
61
+ this._containers.clear();
62
+ }
63
+ dropContainer(toDrop) {
64
+ const id = toDrop.containerId;
65
+ const container = this._containers.get(id);
66
+ if (container !== toDrop)
67
+ throw new Error(`container ${id} not open`);
68
+ container.close();
69
+ this._containers.delete(id);
70
+ }
71
+ resolveContainerId(props) {
72
+ if (typeof props === "object")
73
+ return props.id;
74
+ return this.settings.resolveSetting(WorkspaceSetting.ContainerAlias, (val) => {
75
+ if (Array.isArray(val)) {
76
+ for (const entry of val) {
77
+ if (typeof entry === "object" && entry.name === props && typeof entry.id === "string")
78
+ return entry.id;
79
+ }
80
+ }
81
+ return undefined; // keep going through all settings dictionaries
82
+ }, props);
83
+ }
84
+ }
85
+ exports.ITwinWorkspace = ITwinWorkspace;
86
+ /**
87
+ * A local file holding a WorkspaceContainer.
88
+ * @beta
89
+ */
90
+ class WorkspaceFile {
91
+ constructor(containerId, workspace, opts) {
92
+ this.db = new SQLiteDb_1.SQLiteDb(); // eslint-disable-line @typescript-eslint/naming-convention
93
+ this.onContainerClosed = new core_bentley_1.BeEvent();
94
+ WorkspaceFile.validateContainerId(containerId);
95
+ this.workspace = workspace;
96
+ this.containerId = containerId;
97
+ this.localDbName = (0, path_1.join)(workspace.containerDir, `${this.containerId}.${containerFileExt}`);
98
+ this.iModelOwner = opts === null || opts === void 0 ? void 0 : opts.forIModel;
99
+ }
100
+ get containerFilesDir() { return (0, path_1.join)(this.workspace.filesDir, this.containerId); }
101
+ get isOpen() { return this.db.isOpen; }
102
+ queryFileResource(rscName) {
103
+ const info = this.db.nativeDb.queryEmbeddedFile(rscName);
104
+ if (undefined === info)
105
+ return undefined;
106
+ // since resource names can contain illegal characters, path separators, etc., we make the local file name from its hash, in hex.
107
+ let localFileName = (0, path_1.join)(this.containerFilesDir, (0, crypto_1.createHash)("sha1").update(rscName).digest("hex"));
108
+ if (info.fileExt !== "") // since some applications may expect to see the extension, append it here if it was supplied.
109
+ localFileName = `${localFileName}.${info.fileExt}`;
110
+ return { localFileName, info };
111
+ }
112
+ static noLeadingOrTrailingSpaces(name, msg) {
113
+ if (name.trim() !== name)
114
+ throw new Error(`${msg} [${name}] may not have leading or tailing spaces`);
115
+ }
116
+ static validateContainerId(id) {
117
+ if (id === "" || id.length > 255 || /[\.<>:"/\\"`'|?*\u0000-\u001F]/g.test(id) || /^(con|prn|aux|nul|com\d|lpt\d)$/i.test(id))
118
+ throw new Error(`invalid containerId: [${id}]`);
119
+ this.noLeadingOrTrailingSpaces(id, "containerId");
120
+ }
121
+ async attach(_token) {
122
+ }
123
+ async download() {
124
+ }
125
+ purgeContainerFiles() {
126
+ IModelJsFs_1.IModelJsFs.purgeDirSync(this.containerFilesDir);
127
+ }
128
+ open() {
129
+ this.db.openDb(this.localDbName, core_bentley_1.OpenMode.Readonly);
130
+ }
131
+ close() {
132
+ if (this.isOpen) {
133
+ this.onContainerClosed.raiseEvent();
134
+ this.db.closeDb();
135
+ }
136
+ }
137
+ getString(rscName) {
138
+ return this.db.withSqliteStatement("SELECT value from strings WHERE id=?", (stmt) => {
139
+ stmt.bindString(1, rscName);
140
+ return core_bentley_1.DbResult.BE_SQLITE_ROW === stmt.step() ? stmt.getValueString(0) : undefined;
141
+ });
142
+ }
143
+ getBlob(rscName) {
144
+ return this.db.withSqliteStatement("SELECT value from blobs WHERE id=?", (stmt) => {
145
+ stmt.bindString(1, rscName);
146
+ return core_bentley_1.DbResult.BE_SQLITE_ROW === stmt.step() ? stmt.getValueBlob(0) : undefined;
147
+ });
148
+ }
149
+ getFile(rscName, targetFileName) {
150
+ const file = this.queryFileResource(rscName);
151
+ if (!file)
152
+ return undefined;
153
+ const info = file.info;
154
+ const localFileName = targetFileName !== null && targetFileName !== void 0 ? targetFileName : file.localFileName;
155
+ // check whether the file is already up to date.
156
+ const stat = fs.existsSync(localFileName) && fs.statSync(localFileName);
157
+ if (stat && Math.round(stat.mtimeMs) === info.date && stat.size === info.size)
158
+ return localFileName; // yes, we're done
159
+ // extractEmbeddedFile fails if the file exists or if the directory does not exist
160
+ if (stat)
161
+ fs.removeSync(localFileName);
162
+ else
163
+ IModelJsFs_1.IModelJsFs.recursiveMkDirSync((0, path_1.dirname)(localFileName));
164
+ this.db.nativeDb.extractEmbeddedFile({ name: rscName, localFileName });
165
+ const date = new Date(info.date);
166
+ fs.utimesSync(localFileName, date, date); // set the last-modified date of the file to match date in container
167
+ fs.chmodSync(localFileName, "0444"); // set file readonly
168
+ return localFileName;
169
+ }
170
+ }
171
+ exports.WorkspaceFile = WorkspaceFile;
172
+ /**
173
+ * An editable [[WorkspaceFile]]. This is used by administrators for creating and modifying `WorkspaceContainer`s.
174
+ * For cloud-backed containers, the write token must be obtained before this class may be used. Only one user at at time
175
+ * may be editing.
176
+ * @beta
177
+ */
178
+ class EditableWorkspaceFile extends WorkspaceFile {
179
+ static validateResourceName(name) {
180
+ WorkspaceFile.noLeadingOrTrailingSpaces(name, "resource name");
181
+ if (name.length > 1024)
182
+ throw new Error("resource name too long");
183
+ }
184
+ validateResourceSize(val) {
185
+ const len = typeof val === "string" ? val.length : val.byteLength;
186
+ if (len > (1024 * 1024 * 1024)) // one gigabyte
187
+ throw new Error("value is too large");
188
+ }
189
+ async upload() {
190
+ }
191
+ async lockContainer() {
192
+ this.db.openDb(this.localDbName, core_bentley_1.OpenMode.ReadWrite);
193
+ }
194
+ getFileModifiedTime(localFileName) {
195
+ return Math.round(fs.statSync(localFileName).mtimeMs);
196
+ }
197
+ performWriteSql(rscName, sql, bind) {
198
+ this.db.withSqliteStatement(sql, (stmt) => {
199
+ stmt.bindString(1, rscName);
200
+ bind === null || bind === void 0 ? void 0 : bind(stmt);
201
+ const rc = stmt.step();
202
+ if (core_bentley_1.DbResult.BE_SQLITE_DONE !== rc)
203
+ throw new core_common_1.IModelError(rc, "workspace write error");
204
+ });
205
+ this.db.saveChanges();
206
+ }
207
+ /** Create a new, empty, EditableWorkspaceFile for importing Workspace resources. */
208
+ create() {
209
+ IModelJsFs_1.IModelJsFs.recursiveMkDirSync((0, path_1.dirname)(this.localDbName));
210
+ this.db.createDb(this.localDbName);
211
+ this.db.executeSQL("CREATE TABLE strings(id TEXT PRIMARY KEY NOT NULL,value TEXT)");
212
+ this.db.executeSQL("CREATE TABLE blobs(id TEXT PRIMARY KEY NOT NULL,value BLOB)");
213
+ this.db.saveChanges();
214
+ }
215
+ /** Add a new string resource to this WorkspaceFile.
216
+ * @param rscName The name of the string resource.
217
+ * @param val The string to save.
218
+ */
219
+ addString(rscName, val) {
220
+ EditableWorkspaceFile.validateResourceName(rscName);
221
+ this.validateResourceSize(val);
222
+ this.performWriteSql(rscName, "INSERT INTO strings(id,value) VALUES(?,?)", (stmt) => stmt.bindString(2, val));
223
+ }
224
+ /** Update an existing string resource with a new value.
225
+ * @param rscName The name of the string resource.
226
+ * @param val The new value.
227
+ * @throws if rscName does not exist
228
+ */
229
+ updateString(rscName, val) {
230
+ this.validateResourceSize(val);
231
+ this.performWriteSql(rscName, "UPDATE strings SET value=?2 WHERE id=?1", (stmt) => stmt.bindString(2, val));
232
+ }
233
+ /** Remove a string resource. */
234
+ removeString(rscName) {
235
+ this.performWriteSql(rscName, "DELETE FROM strings WHERE id=?");
236
+ }
237
+ /** Add a new blob resource to this WorkspaceFile.
238
+ * @param rscName The name of the blob resource.
239
+ * @param val The blob to save.
240
+ */
241
+ addBlob(rscName, val) {
242
+ EditableWorkspaceFile.validateResourceName(rscName);
243
+ this.validateResourceSize(val);
244
+ this.performWriteSql(rscName, "INSERT INTO blobs(id,value) VALUES(?,?)", (stmt) => stmt.bindBlob(2, val));
245
+ }
246
+ /** Update an existing blob resource with a new value.
247
+ * @param rscName The name of the blob resource.
248
+ * @param val The new value.
249
+ * @throws if rscName does not exist
250
+ */
251
+ updateBlob(rscName, val) {
252
+ this.validateResourceSize(val);
253
+ this.performWriteSql(rscName, "UPDATE blobs SET value=?2 WHERE id=?1", (stmt) => stmt.bindBlob(2, val));
254
+ }
255
+ /** Remove a blob resource. */
256
+ removeBlob(rscName) {
257
+ this.performWriteSql(rscName, "DELETE FROM blobs WHERE id=?");
258
+ }
259
+ /** Copy the contents of an existing local file into this WorkspaceFile as a file resource.
260
+ * @param rscName The name of the file resource.
261
+ * @param localFileName The name of a local file to be read.
262
+ * @param fileExt The extension (do not include the leading ".") to be appended to the generated fileName
263
+ * when this file is extracted from the WorkspaceContainer. By default the characters after the last "." in `localFileName`
264
+ * are used. Pass this argument to override that.
265
+ */
266
+ addFile(rscName, localFileName, fileExt) {
267
+ EditableWorkspaceFile.validateResourceName(rscName);
268
+ fileExt = fileExt !== null && fileExt !== void 0 ? fileExt : (0, path_1.extname)(localFileName);
269
+ if ((fileExt === null || fileExt === void 0 ? void 0 : fileExt[0]) === ".")
270
+ fileExt = fileExt.slice(1);
271
+ this.db.nativeDb.embedFile({ name: rscName, localFileName, date: this.getFileModifiedTime(localFileName), fileExt });
272
+ }
273
+ /** Replace an existing file resource with the contents of a local file.
274
+ * @param rscName The name of the file resource.
275
+ * @param localFileName The name of a local file to be read.
276
+ * @throws if rscName does not exist
277
+ */
278
+ updateFile(rscName, localFileName) {
279
+ this.queryFileResource(rscName); // throws if not present
280
+ this.db.nativeDb.replaceEmbeddedFile({ name: rscName, localFileName, date: this.getFileModifiedTime(localFileName) });
281
+ }
282
+ /** Remove a file resource. */
283
+ removeFile(rscName) {
284
+ const file = this.queryFileResource(rscName);
285
+ if (file && fs.existsSync(file.localFileName))
286
+ fs.unlinkSync(file.localFileName);
287
+ this.db.nativeDb.removeEmbeddedFile(rscName);
288
+ }
289
+ }
290
+ exports.EditableWorkspaceFile = EditableWorkspaceFile;
291
+ //# sourceMappingURL=Workspace.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Workspace.js","sourceRoot":"","sources":["../../../src/workspace/Workspace.ts"],"names":[],"mappings":"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","sourcesContent":["/*---------------------------------------------------------------------------------------------\r\n* Copyright (c) Bentley Systems, Incorporated. All rights reserved.\r\n* See LICENSE.md in the project root for license terms and full copyright notice.\r\n*--------------------------------------------------------------------------------------------*/\r\n/** @packageDocumentation\r\n * @module Workspace\r\n */\r\n\r\nimport { createHash } from \"crypto\";\r\nimport * as fs from \"fs-extra\";\r\nimport { dirname, extname, join } from \"path\";\r\nimport { AccessToken, BeEvent, DbResult, OpenMode } from \"@itwin/core-bentley\";\r\nimport { IModelError, LocalDirName, LocalFileName } from \"@itwin/core-common\";\r\nimport { IModelDb } from \"../IModelDb\";\r\nimport { IModelJsFs } from \"../IModelJsFs\";\r\nimport { SQLiteDb } from \"../SQLiteDb\";\r\nimport { SqliteStatement } from \"../SqliteStatement\";\r\nimport { ITwinSettings, Settings, SettingsPriority } from \"./Settings\";\r\nimport { NativeLibrary } from \"@bentley/imodeljs-native\";\r\n\r\n/** The names of Settings used by Workspace\r\n * @beta\r\n */\r\nenum WorkspaceSetting {\r\n ContainerAlias = \"workspace/container/alias\",\r\n}\r\n\r\nconst containerFileExt = \"itwin-workspace-container\";\r\n\r\n/**\r\n * The name of a workspace container. This is the user-supplied name of a container, used to specify its *purpose* within a workspace.\r\n * WorkspaceNames can be \"aliased\" by `WorkspaceSetting.ContainerAlias` settings so that \"resolved\" [[WorkspaceContainerId]] that supplies\r\n * the actual container for a WorkspaceContainerName may vary. Also note that more than one WorkspaceContainerName may resolve to the same\r\n * WorkspaceContainerId, if multiple purposes are served by the same container.\r\n * @note there are no constraints on the contents or length of `WorkspaceContainerName`s, although short descriptive names are recommended.\r\n * However, when no alias exists in WorkspaceSetting.ContainerAlias for a WorkspaceContainerName, then the WorkspaceContainerName becomes\r\n * the WorkspaceContainerId, and the constraints on WorkspaceContainerId apply.\r\n * @beta\r\n */\r\nexport type WorkspaceContainerName = string;\r\n\r\n/**\r\n * The unique identifier of a WorkspaceContainer. This becomes the base name for the local file holding the WorkspaceContainer, and is also the\r\n * name of its cloud-storage-container. `WorkspaceContainerName` are resolved to WorkspaceContainerId through `WorkspaceSetting.ContainerAlias` settings,\r\n * so users may not recognize the actual WorkspaceContainerId supplying resources for a WorkspaceContainerName.\r\n *\r\n * `WorkspaceContainerId`s may not:\r\n * - be blank or start or end with a space\r\n * - be longer than 255 characters\r\n * - contain any characters with Unicode values less than 0x20\r\n * - contain characters reserved for filename, device, wildcard, or url syntax (e.g. \"\\.<>:\"/\\\\\"`'|?*\")\r\n * @beta\r\n */\r\nexport type WorkspaceContainerId = string;\r\n\r\n/**\r\n * The name for identifying WorkspaceResources in a [[WorkspaceContainer]].\r\n * * `WorkspaceResourceName`s may not:\r\n * - be blank or start or end with a space\r\n * - be longer than 1024 characters\r\n * @note a single WorkspaceContainer may hold WorkspaceResources of type 'blob', 'string' and 'file', all with the same WorkspaceResourceName.\r\n * @beta\r\n */\r\nexport type WorkspaceResourceName = string;\r\n\r\n/**\r\n * Properties that specify a WorkspaceContainer. This can either be a WorkspaceContainerName or an\r\n * object with a member named `id` that holds a WorkspaceContainerId. If WorkspaceContainerId is supplied,\r\n * it is used directly. Otherwise the name must be resolved via [[Workspace.resolveContainerId]].\r\n * @beta\r\n */\r\nexport type WorkspaceContainerProps = WorkspaceContainerName | { id: WorkspaceContainerId };\r\n\r\n/** Properties that specify a WorkspaceResource within a WorkspaceContainer.\r\n * @beta\r\n */\r\nexport interface WorkspaceResourceProps {\r\n /** the properties of the WorkspaceContainer holding the resource. */\r\n container: WorkspaceContainerProps;\r\n /** the name of the resource within [[container]] */\r\n rscName: WorkspaceResourceName;\r\n}\r\n\r\n/**\r\n * A container of workspace resources. `WorkspaceContainer`s may just be local [[WorkspaceFile]]s, or they may be stored and\r\n * synchronized with cloud blob-store containers. WorkspaceContainers hold WorkspaceResources, each identified by a [[WorkspaceResourceName]].\r\n * Resources of type `string` and `blob` may be loaded directly from the `WorkspaceContainer`. Resources of type `file` are\r\n * copied from the container into a temporary local file so they can be accessed directly.\r\n * @beta\r\n */\r\nexport interface WorkspaceContainer {\r\n /** The WorkspaceContainerId of this container. */\r\n readonly containerId: WorkspaceContainerId;\r\n /** The Workspace that opened this WorkspaceContainer */\r\n readonly workspace: Workspace;\r\n /** If present, IModelDb that owns this [[WorkspaceContainer]]. The lifetime of this container is paired with the iModelDb. */\r\n readonly iModelOwner?: IModelDb;\r\n /** the directory for extracting file resources. */\r\n readonly containerFilesDir: LocalDirName;\r\n /** event raised when the container is closed. */\r\n readonly onContainerClosed: BeEvent<() => void>;\r\n /** Get a string resource from this container, if present. */\r\n getString(rscName: WorkspaceResourceName): string | undefined;\r\n\r\n /** Get a blob resource from this container, if present. */\r\n getBlob(rscName: WorkspaceResourceName): Uint8Array | undefined;\r\n\r\n /** Extract a local copy of a file resource from this container, if present.\r\n * @param rscName The name of the file resource in the WorkspaceContainer\r\n * @param targetFileName optional name for extracted file. Some applications require files in specific locations or filenames. If\r\n * you know the full path to use for the extracted file, you can supply it. Generally, it is best to *not* supply the filename and\r\n * keep the extracted files in the [[containerFilesDir]].\r\n * @returns the full path to a file on the local filesystem.\r\n * @note The file is copied from the container into the local filesystem so it may be accessed directly. This happens only\r\n * as necessary, if the local file doesn't exist, or if it is out-of-date because it was updated in the container.\r\n * For this reason, you should not save the local file name, and instead call this method every time you access it, so its\r\n * content is always holds the correct version.\r\n * @note The filename will be a hash value, not the resource name.\r\n * @note Workspace resource files are set readonly as they are copied from the container.\r\n * To edit them, you must first copy them to another location.\r\n */\r\n getFile(rscName: WorkspaceResourceName, targetFileName?: LocalFileName): LocalFileName | undefined;\r\n}\r\n\r\n/**\r\n * Options supplied when opening a WorkspaceContainer.\r\n * @beta\r\n */\r\nexport interface WorkspaceContainerOpts {\r\n /** If present, the container will be closed and removed when the iModel is closed. */\r\n forIModel?: IModelDb;\r\n}\r\n\r\n/**\r\n * Options for constructing a [[Workspace]].\r\n * @beta\r\n */\r\nexport interface WorkspaceOpts {\r\n /** The local directory for the WorkspaceContainer files. The [[Workspace]] will (only) look in this directory\r\n * for files named `${this.containerId}.itwin-workspace-container`.\r\n * @note if not supplied, defaults to `iTwin/Workspace` in the user-local folder.\r\n */\r\n containerDir?: LocalDirName;\r\n /** A local directory to store temporary files extracted for file-resources.\r\n * @note if not supplied, defaults to `a folder named \"Files\" inside [[containerDir]]\r\n */\r\n filesDir?: LocalDirName;\r\n}\r\n\r\n/**\r\n * Settings and resources that customize an application for the current session.\r\n * See [Workspaces]($docs/learning/backend/Workspace)\r\n * @beta\r\n */\r\nexport interface Workspace {\r\n /** The local directory for the WorkspaceContainer files with the name `${containerId}.itwin-workspace-container`. */\r\n readonly containerDir: LocalDirName;\r\n /** the local directory where this Workspace will store temporary files extracted for file-resources. */\r\n readonly filesDir: LocalDirName;\r\n /** The [[Settings]] for this Workspace */\r\n readonly settings: Settings;\r\n /**\r\n * Resolve a WorkspaceContainerProps to a WorkspaceContainerId. If props is an object with an `id` member, that value is returned unchanged.\r\n * If it is a string, then the highest priority [[WorkspaceSetting.ContainerAlias]] setting with an entry for the WorkspaceContainerName\r\n * is used. If no WorkspaceSetting.ContainerAlias entry for the WorkspaceContainerName can be found, the name is returned as the id.\r\n */\r\n resolveContainerId(props: WorkspaceContainerProps): WorkspaceContainerId;\r\n /**\r\n * Get an open [[WorkspaceContainer]]. If the container is present but not open, it is opened first.\r\n * If it is not present or not up-to-date, it is downloaded first.\r\n * @returns a Promise that is resolved when the container is local, opened, and available for access.\r\n */\r\n getContainer(props: WorkspaceContainerProps, opts?: WorkspaceContainerOpts): Promise<WorkspaceContainer>;\r\n /** Load a WorkspaceResource of type string, parse it, and add it to the current Settings for this Workspace.\r\n * @note settingsRsc must specify a resource holding a stringified JSON representation of a [[SettingDictionary]]\r\n * @returns a Promise that is resolved when the settings resource has been loaded.\r\n */\r\n loadSettingsDictionary(settingRsc: WorkspaceResourceProps, priority: SettingsPriority): Promise<void>;\r\n /** Close and remove a currently opened [[WorkspaceContainer]] from this Workspace. */\r\n dropContainer(container: WorkspaceContainer): void;\r\n /** Close this Workspace. All currently opened WorkspaceContainers are dropped. */\r\n close(): void;\r\n}\r\n\r\n/** @internal */\r\nexport class ITwinWorkspace implements Workspace {\r\n private _containers = new Map<WorkspaceContainerId, WorkspaceFile>();\r\n public readonly filesDir: LocalDirName;\r\n public readonly containerDir: LocalDirName;\r\n public readonly settings: Settings;\r\n\r\n public constructor(opts?: WorkspaceOpts) {\r\n this.settings = new ITwinSettings();\r\n this.containerDir = opts?.containerDir ?? join(NativeLibrary.defaultLocalDir, \"iTwin\", \"Workspace\");\r\n this.filesDir = opts?.filesDir ?? join(this.containerDir, \"Files\");\r\n }\r\n\r\n public async getContainer(props: WorkspaceContainerProps, opts?: WorkspaceContainerOpts): Promise<WorkspaceContainer> {\r\n const id = this.resolveContainerId(props);\r\n if (undefined === id)\r\n throw new Error(`can't resolve container name [${props}]`);\r\n let container = this._containers.get(id);\r\n if (container)\r\n return container;\r\n\r\n container = new WorkspaceFile(id, this, opts);\r\n container.open();\r\n this._containers.set(id, container);\r\n if (opts?.forIModel)\r\n opts.forIModel.onBeforeClose.addOnce(() => this.dropContainer(container!));\r\n return container;\r\n }\r\n\r\n public async loadSettingsDictionary(settingRsc: WorkspaceResourceProps, priority: SettingsPriority) {\r\n const container = await this.getContainer(settingRsc.container);\r\n const setting = container.getString(settingRsc.rscName);\r\n if (undefined === setting)\r\n throw new Error(`could not load setting resource ${settingRsc.rscName}`);\r\n\r\n this.settings.addJson(`${container.containerId}/${settingRsc.rscName}`, priority, setting);\r\n }\r\n\r\n public close() {\r\n for (const [_id, container] of this._containers)\r\n container.close();\r\n this._containers.clear();\r\n }\r\n\r\n public dropContainer(toDrop: WorkspaceContainer) {\r\n const id = toDrop.containerId;\r\n const container = this._containers.get(id);\r\n if (container !== toDrop)\r\n throw new Error(`container ${id} not open`);\r\n container.close();\r\n this._containers.delete(id);\r\n }\r\n\r\n public resolveContainerId(props: WorkspaceContainerProps): WorkspaceContainerId {\r\n if (typeof props === \"object\")\r\n return props.id;\r\n return this.settings.resolveSetting(WorkspaceSetting.ContainerAlias, (val) => {\r\n if (Array.isArray(val)) {\r\n for (const entry of val) {\r\n if (typeof entry === \"object\" && entry.name === props && typeof entry.id === \"string\")\r\n return entry.id;\r\n }\r\n }\r\n return undefined; // keep going through all settings dictionaries\r\n }, props)!;\r\n\r\n }\r\n}\r\n\r\n/**\r\n * A local file holding a WorkspaceContainer.\r\n * @beta\r\n */\r\nexport class WorkspaceFile implements WorkspaceContainer {\r\n protected readonly db = new SQLiteDb(); // eslint-disable-line @typescript-eslint/naming-convention\r\n public readonly workspace: Workspace;\r\n public readonly containerId: WorkspaceContainerId;\r\n public readonly localDbName: LocalDirName;\r\n public readonly iModelOwner?: IModelDb;\r\n public readonly onContainerClosed = new BeEvent<() => void>();\r\n\r\n public get containerFilesDir() { return join(this.workspace.filesDir, this.containerId); }\r\n public get isOpen() { return this.db.isOpen; }\r\n\r\n protected queryFileResource(rscName: WorkspaceResourceName) {\r\n const info = this.db.nativeDb.queryEmbeddedFile(rscName);\r\n if (undefined === info)\r\n return undefined;\r\n\r\n // since resource names can contain illegal characters, path separators, etc., we make the local file name from its hash, in hex.\r\n let localFileName = join(this.containerFilesDir, createHash(\"sha1\").update(rscName).digest(\"hex\"));\r\n if (info.fileExt !== \"\") // since some applications may expect to see the extension, append it here if it was supplied.\r\n localFileName = `${localFileName}.${info.fileExt}`;\r\n return { localFileName, info };\r\n }\r\n\r\n protected static noLeadingOrTrailingSpaces(name: string, msg: string) {\r\n if (name.trim() !== name)\r\n throw new Error(`${msg} [${name}] may not have leading or tailing spaces`);\r\n }\r\n\r\n private static validateContainerId(id: WorkspaceContainerId) {\r\n if (id === \"\" || id.length > 255 || /[\\.<>:\"/\\\\\"`'|?*\\u0000-\\u001F]/g.test(id) || /^(con|prn|aux|nul|com\\d|lpt\\d)$/i.test(id))\r\n throw new Error(`invalid containerId: [${id}]`);\r\n this.noLeadingOrTrailingSpaces(id, \"containerId\");\r\n }\r\n\r\n public constructor(containerId: WorkspaceContainerId, workspace: Workspace, opts?: WorkspaceContainerOpts) {\r\n WorkspaceFile.validateContainerId(containerId);\r\n this.workspace = workspace;\r\n this.containerId = containerId;\r\n this.localDbName = join(workspace.containerDir, `${this.containerId}.${containerFileExt}`);\r\n this.iModelOwner = opts?.forIModel;\r\n }\r\n\r\n public async attach(_token: AccessToken) {\r\n }\r\n\r\n public async download() {\r\n }\r\n\r\n public purgeContainerFiles() {\r\n IModelJsFs.purgeDirSync(this.containerFilesDir);\r\n }\r\n\r\n public open(): void {\r\n this.db.openDb(this.localDbName, OpenMode.Readonly);\r\n }\r\n\r\n public close(): void {\r\n if (this.isOpen) {\r\n this.onContainerClosed.raiseEvent();\r\n this.db.closeDb();\r\n }\r\n }\r\n\r\n public getString(rscName: WorkspaceResourceName): string | undefined {\r\n return this.db.withSqliteStatement(\"SELECT value from strings WHERE id=?\", (stmt) => {\r\n stmt.bindString(1, rscName);\r\n return DbResult.BE_SQLITE_ROW === stmt.step() ? stmt.getValueString(0) : undefined;\r\n });\r\n }\r\n\r\n public getBlob(rscName: WorkspaceResourceName): Uint8Array | undefined {\r\n return this.db.withSqliteStatement(\"SELECT value from blobs WHERE id=?\", (stmt) => {\r\n stmt.bindString(1, rscName);\r\n return DbResult.BE_SQLITE_ROW === stmt.step() ? stmt.getValueBlob(0) : undefined;\r\n });\r\n }\r\n\r\n public getFile(rscName: WorkspaceResourceName, targetFileName?: LocalFileName): LocalFileName | undefined {\r\n const file = this.queryFileResource(rscName);\r\n if (!file)\r\n return undefined;\r\n\r\n const info = file.info;\r\n const localFileName = targetFileName ?? file.localFileName;\r\n\r\n // check whether the file is already up to date.\r\n const stat = fs.existsSync(localFileName) && fs.statSync(localFileName);\r\n if (stat && Math.round(stat.mtimeMs) === info.date && stat.size === info.size)\r\n return localFileName; // yes, we're done\r\n\r\n // extractEmbeddedFile fails if the file exists or if the directory does not exist\r\n if (stat)\r\n fs.removeSync(localFileName);\r\n else\r\n IModelJsFs.recursiveMkDirSync(dirname(localFileName));\r\n\r\n this.db.nativeDb.extractEmbeddedFile({ name: rscName, localFileName });\r\n const date = new Date(info.date);\r\n fs.utimesSync(localFileName, date, date); // set the last-modified date of the file to match date in container\r\n fs.chmodSync(localFileName, \"0444\"); // set file readonly\r\n return localFileName;\r\n }\r\n}\r\n\r\n/**\r\n * An editable [[WorkspaceFile]]. This is used by administrators for creating and modifying `WorkspaceContainer`s.\r\n * For cloud-backed containers, the write token must be obtained before this class may be used. Only one user at at time\r\n * may be editing.\r\n * @beta\r\n */\r\nexport class EditableWorkspaceFile extends WorkspaceFile {\r\n private static validateResourceName(name: WorkspaceResourceName) {\r\n WorkspaceFile.noLeadingOrTrailingSpaces(name, \"resource name\");\r\n if (name.length > 1024)\r\n throw new Error(\"resource name too long\");\r\n }\r\n\r\n private validateResourceSize(val: Uint8Array | string) {\r\n const len = typeof val === \"string\" ? val.length : val.byteLength;\r\n if (len > (1024 * 1024 * 1024)) // one gigabyte\r\n throw new Error(\"value is too large\");\r\n }\r\n\r\n public async upload() {\r\n }\r\n\r\n public async lockContainer() {\r\n this.db.openDb(this.localDbName, OpenMode.ReadWrite);\r\n }\r\n\r\n private getFileModifiedTime(localFileName: LocalFileName): number {\r\n return Math.round(fs.statSync(localFileName).mtimeMs);\r\n }\r\n\r\n private performWriteSql(rscName: WorkspaceResourceName, sql: string, bind?: (stmt: SqliteStatement) => void) {\r\n this.db.withSqliteStatement(sql, (stmt) => {\r\n stmt.bindString(1, rscName);\r\n bind?.(stmt);\r\n const rc = stmt.step();\r\n if (DbResult.BE_SQLITE_DONE !== rc)\r\n throw new IModelError(rc, \"workspace write error\");\r\n });\r\n this.db.saveChanges();\r\n }\r\n\r\n /** Create a new, empty, EditableWorkspaceFile for importing Workspace resources. */\r\n public create() {\r\n IModelJsFs.recursiveMkDirSync(dirname(this.localDbName));\r\n this.db.createDb(this.localDbName);\r\n this.db.executeSQL(\"CREATE TABLE strings(id TEXT PRIMARY KEY NOT NULL,value TEXT)\");\r\n this.db.executeSQL(\"CREATE TABLE blobs(id TEXT PRIMARY KEY NOT NULL,value BLOB)\");\r\n this.db.saveChanges();\r\n }\r\n\r\n /** Add a new string resource to this WorkspaceFile.\r\n * @param rscName The name of the string resource.\r\n * @param val The string to save.\r\n */\r\n public addString(rscName: WorkspaceResourceName, val: string): void {\r\n EditableWorkspaceFile.validateResourceName(rscName);\r\n this.validateResourceSize(val);\r\n this.performWriteSql(rscName, \"INSERT INTO strings(id,value) VALUES(?,?)\", (stmt) => stmt.bindString(2, val));\r\n }\r\n\r\n /** Update an existing string resource with a new value.\r\n * @param rscName The name of the string resource.\r\n * @param val The new value.\r\n * @throws if rscName does not exist\r\n */\r\n public updateString(rscName: WorkspaceResourceName, val: string): void {\r\n this.validateResourceSize(val);\r\n this.performWriteSql(rscName, \"UPDATE strings SET value=?2 WHERE id=?1\", (stmt) => stmt.bindString(2, val));\r\n }\r\n\r\n /** Remove a string resource. */\r\n public removeString(rscName: WorkspaceResourceName): void {\r\n this.performWriteSql(rscName, \"DELETE FROM strings WHERE id=?\");\r\n }\r\n\r\n /** Add a new blob resource to this WorkspaceFile.\r\n * @param rscName The name of the blob resource.\r\n * @param val The blob to save.\r\n */\r\n public addBlob(rscName: WorkspaceResourceName, val: Uint8Array): void {\r\n EditableWorkspaceFile.validateResourceName(rscName);\r\n this.validateResourceSize(val);\r\n this.performWriteSql(rscName, \"INSERT INTO blobs(id,value) VALUES(?,?)\", (stmt) => stmt.bindBlob(2, val));\r\n }\r\n\r\n /** Update an existing blob resource with a new value.\r\n * @param rscName The name of the blob resource.\r\n * @param val The new value.\r\n * @throws if rscName does not exist\r\n */\r\n public updateBlob(rscName: WorkspaceResourceName, val: Uint8Array): void {\r\n this.validateResourceSize(val);\r\n this.performWriteSql(rscName, \"UPDATE blobs SET value=?2 WHERE id=?1\", (stmt) => stmt.bindBlob(2, val));\r\n }\r\n\r\n /** Remove a blob resource. */\r\n public removeBlob(rscName: WorkspaceResourceName): void {\r\n this.performWriteSql(rscName, \"DELETE FROM blobs WHERE id=?\");\r\n }\r\n\r\n /** Copy the contents of an existing local file into this WorkspaceFile as a file resource.\r\n * @param rscName The name of the file resource.\r\n * @param localFileName The name of a local file to be read.\r\n * @param fileExt The extension (do not include the leading \".\") to be appended to the generated fileName\r\n * when this file is extracted from the WorkspaceContainer. By default the characters after the last \".\" in `localFileName`\r\n * are used. Pass this argument to override that.\r\n */\r\n public addFile(rscName: WorkspaceResourceName, localFileName: LocalFileName, fileExt?: string): void {\r\n EditableWorkspaceFile.validateResourceName(rscName);\r\n fileExt = fileExt ?? extname(localFileName);\r\n if (fileExt?.[0] === \".\")\r\n fileExt = fileExt.slice(1);\r\n this.db.nativeDb.embedFile({ name: rscName, localFileName, date: this.getFileModifiedTime(localFileName), fileExt });\r\n }\r\n\r\n /** Replace an existing file resource with the contents of a local file.\r\n * @param rscName The name of the file resource.\r\n * @param localFileName The name of a local file to be read.\r\n * @throws if rscName does not exist\r\n */\r\n public updateFile(rscName: WorkspaceResourceName, localFileName: LocalFileName): void {\r\n this.queryFileResource(rscName); // throws if not present\r\n this.db.nativeDb.replaceEmbeddedFile({ name: rscName, localFileName, date: this.getFileModifiedTime(localFileName) });\r\n }\r\n\r\n /** Remove a file resource. */\r\n public removeFile(rscName: WorkspaceResourceName): void {\r\n const file = this.queryFileResource(rscName);\r\n if (file && fs.existsSync(file.localFileName))\r\n fs.unlinkSync(file.localFileName);\r\n this.db.nativeDb.removeEmbeddedFile(rscName);\r\n }\r\n\r\n}\r\n\r\n"]}