@itwin/core-backend
Version:
iTwin.js backend components
159 lines • 8.87 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
* @module RpcInterface
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.RpcBriefcaseUtility = void 0;
const core_bentley_1 = require("@itwin/core-bentley");
const core_common_1 = require("@itwin/core-common");
const BackendLoggerCategory_1 = require("../BackendLoggerCategory");
const BriefcaseManager_1 = require("../BriefcaseManager");
const CheckpointManager_1 = require("../CheckpointManager");
const IModelDb_1 = require("../IModelDb");
const IModelHost_1 = require("../IModelHost");
const IModelJsFs_1 = require("../IModelJsFs");
const Symbols_1 = require("../internal/Symbols");
const loggerCategory = BackendLoggerCategory_1.BackendLoggerCategory.IModelDb;
/**
* Utility to open the iModel for RPC interfaces
* @internal
*/
class RpcBriefcaseUtility {
static async downloadAndOpen(args) {
const { activity, tokenProps } = args;
const accessToken = activity.accessToken;
(0, core_bentley_1.assert)(undefined !== tokenProps.iModelId);
const iModelId = tokenProps.iModelId;
let myBriefcaseIds;
if (args.syncMode === core_common_1.SyncMode.PullOnly) {
myBriefcaseIds = [0]; // PullOnly means briefcaseId 0
}
else {
// check with iModelHub and see if we already have acquired any briefcaseIds
myBriefcaseIds = await IModelHost_1.IModelHost[Symbols_1._hubAccess].getMyBriefcaseIds({ accessToken, iModelId });
}
const resolvers = args.fileNameResolvers ?? [(arg) => BriefcaseManager_1.BriefcaseManager.getFileName(arg)];
// see if we can open any of the briefcaseIds we already acquired from iModelHub
if (resolvers) {
for (const resolver of resolvers) {
for (const briefcaseId of myBriefcaseIds) {
const fileName = resolver({ briefcaseId, iModelId });
if (IModelJsFs_1.IModelJsFs.existsSync(fileName)) {
const briefcaseDb = IModelDb_1.BriefcaseDb.findByFilename(fileName);
if (briefcaseDb !== undefined) {
if (briefcaseDb.isBriefcaseDb()) {
return briefcaseDb;
}
else {
throw new core_common_1.IModelError(core_bentley_1.IModelStatus.AlreadyOpen, "iModel is already open as a SnapshotDb");
}
}
try {
if (args.forceDownload)
throw new Error(); // causes delete below
const db = await IModelDb_1.BriefcaseDb.open({ fileName });
if (db.changeset.id !== tokenProps.changeset?.id) {
(0, core_bentley_1.assert)(undefined !== tokenProps.changeset);
const toIndex = tokenProps.changeset?.index ??
(await IModelHost_1.IModelHost[Symbols_1._hubAccess].getChangesetFromVersion({ accessToken, iModelId, version: core_common_1.IModelVersion.asOfChangeSet(tokenProps.changeset.id) })).index;
await BriefcaseManager_1.BriefcaseManager.pullAndApplyChangesets(db, { accessToken, toIndex });
}
return db;
}
catch (error) {
if (!(error.errorNumber === core_bentley_1.IModelStatus.AlreadyOpen))
// somehow we have this briefcaseId and the file exists, but we can't open it. Delete it.
await BriefcaseManager_1.BriefcaseManager.deleteBriefcaseFiles(fileName, accessToken);
}
}
}
}
}
// no local briefcase available. Download one and open it.
(0, core_bentley_1.assert)(undefined !== tokenProps.iTwinId);
const request = {
accessToken,
iTwinId: tokenProps.iTwinId,
iModelId,
briefcaseId: args.syncMode === core_common_1.SyncMode.PullOnly ? 0 : undefined, // if briefcaseId is undefined, we'll acquire a new one.
};
const props = await BriefcaseManager_1.BriefcaseManager.downloadBriefcase(request);
return IModelDb_1.BriefcaseDb.open(props);
}
static _briefcasePromises = new Map();
static async openBriefcase(args) {
const key = `${args.tokenProps.iModelId}:${args.tokenProps.changeset?.id}:${args.tokenProps.changeset?.index}:${args.syncMode}`;
const cachedPromise = this._briefcasePromises.get(key);
if (cachedPromise)
return cachedPromise;
try {
const briefcasePromise = this.downloadAndOpen(args); // save the fact that we're working on downloading so if we timeout, we'll reuse this request.
this._briefcasePromises.set(key, briefcasePromise);
return await briefcasePromise;
}
finally {
this._briefcasePromises.delete(key); // the download and open is now done
}
}
/** find a previously opened iModel for RPC.
* @param accessToken necessary (only) for V2 checkpoints to refresh access token in daemon if it has expired. We use the accessToken of the current RPC request
* to refresh the daemon, even though it will be used for all authorized users.
* @param the IModelRpcProps to locate the opened iModel.
*/
static async findOpenIModel(accessToken, iModel) {
const iModelDb = IModelDb_1.IModelDb.findByKey(iModel.key);
// call refreshContainer, just in case this is a V2 checkpoint whose sasToken is about to expire, or its default transaction is about to be restarted.
await iModelDb.refreshContainerForRpc(accessToken);
return iModelDb;
}
/**
* Download and open a checkpoint or briefcase, ensuring the operation completes within a default timeout. If the time to open exceeds the timeout period,
* a RpcPendingResponse exception is thrown
*/
static async open(args) {
const { activity, tokenProps, syncMode } = args;
core_bentley_1.Logger.logTrace(loggerCategory, "RpcBriefcaseUtility.open", tokenProps);
const timeout = args.timeout ?? 1000;
if (syncMode === core_common_1.SyncMode.PullOnly || syncMode === core_common_1.SyncMode.PullAndPush) {
const briefcaseDb = await core_bentley_1.BeDuration.race(timeout, this.openBriefcase(args));
if (briefcaseDb === undefined) {
core_bentley_1.Logger.logTrace(loggerCategory, "Open briefcase - pending", tokenProps);
throw new core_common_1.RpcPendingResponse(); // eslint-disable-line @typescript-eslint/only-throw-error
}
// note: usage is logged in the function BriefcaseManager.downloadNewBriefcaseAndOpen
return briefcaseDb;
}
if (!tokenProps.iModelId || !tokenProps.iTwinId || !tokenProps.changeset)
throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadArg, "invalid arguments");
const checkpoint = {
iModelId: tokenProps.iModelId,
iTwinId: tokenProps.iTwinId,
changeset: tokenProps.changeset,
accessToken: activity.accessToken,
};
// opening a checkpoint.
let db;
// first check if it's already open
db = IModelDb_1.SnapshotDb.tryFindByKey(CheckpointManager_1.CheckpointManager.getKey(checkpoint));
if (db) {
core_bentley_1.Logger.logTrace(loggerCategory, "Checkpoint was already open", tokenProps);
return db;
}
// now try V2 checkpoint
db = await IModelDb_1.SnapshotDb.openCheckpointFromRpc(checkpoint);
core_bentley_1.Logger.logTrace(loggerCategory, "using V2 checkpoint", tokenProps);
return db;
}
static async openWithTimeout(activity, tokenProps, syncMode, timeout = 1000) {
if (tokenProps.iModelId)
await IModelHost_1.IModelHost.tileStorage?.initialize(tokenProps.iModelId);
// eslint-disable-next-line @typescript-eslint/no-deprecated
return (await this.open({ activity, tokenProps, syncMode, timeout })).toJSON();
}
}
exports.RpcBriefcaseUtility = RpcBriefcaseUtility;
//# sourceMappingURL=RpcBriefcaseUtility.js.map
;