@itwin/core-backend
Version:
iTwin.js backend components
155 lines • 8.24 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
*/
import { assert, BeDuration, IModelStatus, Logger } from "@itwin/core-bentley";
import { IModelError, IModelVersion, RpcPendingResponse, SyncMode, } from "@itwin/core-common";
import { BackendLoggerCategory } from "../BackendLoggerCategory";
import { BriefcaseManager } from "../BriefcaseManager";
import { CheckpointManager } from "../CheckpointManager";
import { BriefcaseDb, IModelDb, SnapshotDb } from "../IModelDb";
import { IModelHost } from "../IModelHost";
import { IModelJsFs } from "../IModelJsFs";
import { _hubAccess } from "../internal/Symbols";
const loggerCategory = BackendLoggerCategory.IModelDb;
/**
* Utility to open the iModel for RPC interfaces
* @internal
*/
export class RpcBriefcaseUtility {
static async downloadAndOpen(args) {
const { activity, tokenProps } = args;
const accessToken = activity.accessToken;
assert(undefined !== tokenProps.iModelId);
const iModelId = tokenProps.iModelId;
let myBriefcaseIds;
if (args.syncMode === SyncMode.PullOnly) {
myBriefcaseIds = [0]; // PullOnly means briefcaseId 0
}
else {
// check with iModelHub and see if we already have acquired any briefcaseIds
myBriefcaseIds = await IModelHost[_hubAccess].getMyBriefcaseIds({ accessToken, iModelId });
}
const resolvers = args.fileNameResolvers ?? [(arg) => 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.existsSync(fileName)) {
const briefcaseDb = BriefcaseDb.findByFilename(fileName);
if (briefcaseDb !== undefined) {
if (briefcaseDb.isBriefcaseDb()) {
return briefcaseDb;
}
else {
throw new IModelError(IModelStatus.AlreadyOpen, "iModel is already open as a SnapshotDb");
}
}
try {
if (args.forceDownload)
throw new Error(); // causes delete below
const db = await BriefcaseDb.open({ fileName });
if (db.changeset.id !== tokenProps.changeset?.id) {
assert(undefined !== tokenProps.changeset);
const toIndex = tokenProps.changeset?.index ??
(await IModelHost[_hubAccess].getChangesetFromVersion({ accessToken, iModelId, version: IModelVersion.asOfChangeSet(tokenProps.changeset.id) })).index;
await BriefcaseManager.pullAndApplyChangesets(db, { accessToken, toIndex });
}
return db;
}
catch (error) {
if (!(error.errorNumber === IModelStatus.AlreadyOpen))
// somehow we have this briefcaseId and the file exists, but we can't open it. Delete it.
await BriefcaseManager.deleteBriefcaseFiles(fileName, accessToken);
}
}
}
}
}
// no local briefcase available. Download one and open it.
assert(undefined !== tokenProps.iTwinId);
const request = {
accessToken,
iTwinId: tokenProps.iTwinId,
iModelId,
briefcaseId: args.syncMode === SyncMode.PullOnly ? 0 : undefined, // if briefcaseId is undefined, we'll acquire a new one.
};
const props = await BriefcaseManager.downloadBriefcase(request);
return 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.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;
Logger.logTrace(loggerCategory, "RpcBriefcaseUtility.open", tokenProps);
const timeout = args.timeout ?? 1000;
if (syncMode === SyncMode.PullOnly || syncMode === SyncMode.PullAndPush) {
const briefcaseDb = await BeDuration.race(timeout, this.openBriefcase(args));
if (briefcaseDb === undefined) {
Logger.logTrace(loggerCategory, "Open briefcase - pending", tokenProps);
throw new 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 IModelError(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 = SnapshotDb.tryFindByKey(CheckpointManager.getKey(checkpoint));
if (db) {
Logger.logTrace(loggerCategory, "Checkpoint was already open", tokenProps);
return db;
}
// now try V2 checkpoint
db = await SnapshotDb.openCheckpointFromRpc(checkpoint);
Logger.logTrace(loggerCategory, "using V2 checkpoint", tokenProps);
return db;
}
static async openWithTimeout(activity, tokenProps, syncMode, timeout = 1000) {
if (tokenProps.iModelId)
await IModelHost.tileStorage?.initialize(tokenProps.iModelId);
// eslint-disable-next-line @typescript-eslint/no-deprecated
return (await this.open({ activity, tokenProps, syncMode, timeout })).toJSON();
}
}
//# sourceMappingURL=RpcBriefcaseUtility.js.map