UNPKG

@itwin/core-backend

Version:
314 lines • 15.4 kB
/*--------------------------------------------------------------------------------------------- * 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, BentleyStatus, CompressedId64Set, Id64, IModelStatus, Logger, } from "@itwin/core-bentley"; import { Code, ImageSourceFormat, IModel, IModelError, IModelReadRpcInterface, NoContentError, RpcInterface, RpcManager, RpcPendingResponse, SyncMode, ViewStoreRpc, } from "@itwin/core-common"; import { BackendLoggerCategory } from "../BackendLoggerCategory"; import { SpatialCategory } from "../Category"; import { ConcurrentQuery } from "../ConcurrentQuery"; import { CustomViewState3dCreator } from "../CustomViewState3dCreator"; import { generateGeometrySummaries } from "../GeometrySummary"; import { PromiseMemoizer } from "../PromiseMemoizer"; import { RpcTrace } from "../rpc/tracing"; import { ViewStateHydrator } from "../ViewStateHydrator"; import { RpcBriefcaseUtility } from "./RpcBriefcaseUtility"; import { _nativeDb } from "../internal/Symbols"; class ViewStateRequestMemoizer extends PromiseMemoizer { _timeoutMs; static _instance; static async perform(props) { if (!this._instance) this._instance = new ViewStateRequestMemoizer(); return this._instance.perform(props); } constructor() { const memoize = async (props) => { const db = await RpcBriefcaseUtility.findOpenIModel(props.accessToken, props.tokenProps); const viewCreator = new CustomViewState3dCreator(db); return viewCreator.getCustomViewState3dData(props.options); }; const stringify = (props) => { const token = props.tokenProps; const modelIds = props.options.modelIds; return `${token.key}-${token.iTwinId}-${token.iModelId}-${token.changeset?.id}:${modelIds}`; }; super(memoize, stringify); this._timeoutMs = 20 * 1000; } async perform(props) { const memo = this.memoize(props); // Rejections must be caught so that the memoization entry is deleted. await BeDuration.race(this._timeoutMs, memo.promise).catch(() => undefined); if (memo.isPending) throw new RpcPendingResponse(); // eslint-disable-line @typescript-eslint/only-throw-error this.deleteMemoized(props); if (memo.isFulfilled) { assert(undefined !== memo.result); return memo.result; } assert(memo.isRejected); throw memo.error; } } function currentActivity() { return RpcTrace.expectCurrentActivity; } async function getIModelForRpc(tokenProps) { return RpcBriefcaseUtility.findOpenIModel(RpcTrace.expectCurrentActivity.accessToken, tokenProps); } /** The backend implementation of IModelReadRpcInterface. * @internal */ export class IModelReadRpcImpl extends RpcInterface { static register() { RpcManager.registerImpl(IModelReadRpcInterface, IModelReadRpcImpl); } async getConnectionProps(tokenProps) { return RpcBriefcaseUtility.openWithTimeout(currentActivity(), tokenProps, SyncMode.FixedVersion); } async getCustomViewState3dData(tokenProps, options) { const accessToken = currentActivity().accessToken; return ViewStateRequestMemoizer.perform({ accessToken, tokenProps, options }); } async hydrateViewState(tokenProps, options) { const iModelDb = await getIModelForRpc(tokenProps); const viewHydrater = new ViewStateHydrator(iModelDb); return viewHydrater.getHydrateResponseProps(options); } async queryAllUsedSpatialSubCategories(tokenProps) { const iModelDb = await getIModelForRpc(tokenProps); return iModelDb.queryAllUsedSpatialSubCategories(); } async querySubCategories(tokenProps, compressedCategoryIds) { const iModelDb = await getIModelForRpc(tokenProps); const decompressedIds = CompressedId64Set.decompressArray(compressedCategoryIds); return iModelDb.querySubCategories(decompressedIds); } async queryRows(tokenProps, request) { const iModelDb = await getIModelForRpc(tokenProps); if (iModelDb.isReadonly && request.usePrimaryConn === true) { Logger.logWarning(BackendLoggerCategory.IModelDb, "usePrimaryConn is only supported on imodel that is opened in read/write mode. The option will be ignored.", request); request.usePrimaryConn = false; } return ConcurrentQuery.executeQueryRequest(iModelDb[_nativeDb], request); } async queryBlob(tokenProps, request) { const iModelDb = await getIModelForRpc(tokenProps); if (iModelDb.isReadonly && request.usePrimaryConn === true) { Logger.logWarning(BackendLoggerCategory.IModelDb, "usePrimaryConn is only supported on imodel that is opened in read/write mode. The option will be ignored.", request); request.usePrimaryConn = false; } return ConcurrentQuery.executeBlobRequest(iModelDb[_nativeDb], request); } async queryModelRanges(tokenProps, modelIds) { const results = await this.queryModelExtents(tokenProps, modelIds); if (results.length === 1 && results[0].status !== IModelStatus.Success) throw new IModelError(results[0].status, "error querying model range"); return results.filter((x) => x.status === IModelStatus.Success).map((x) => x.extents); } async queryModelExtents(tokenProps, modelIds) { const iModel = await getIModelForRpc(tokenProps); return iModel.models.queryExtents(modelIds); } async getModelProps(tokenProps, modelIdsList) { const modelIds = new Set(modelIdsList); const iModelDb = await getIModelForRpc(tokenProps); const modelJsonArray = []; for (const id of modelIds) { try { const modelProps = iModelDb.models.getModelProps(id); modelJsonArray.push(modelProps); } catch (error) { if (modelIds.size === 1) throw error; // if they're asking for more than one model, don't throw on error. } } return modelJsonArray; } async queryModelProps(tokenProps, params) { const ids = await this.queryEntityIds(tokenProps, params); return this.getModelProps(tokenProps, [...ids]); } async getElementProps(tokenProps, elementIdsList) { const elementIds = new Set(elementIdsList); const iModelDb = await getIModelForRpc(tokenProps); const elementProps = []; for (const id of elementIds) { try { elementProps.push(iModelDb.elements.getElementProps({ id })); } catch (error) { if (elementIds.size === 1) throw error; // if they're asking for more than one element, don't throw on error. } } return elementProps; } async loadElementProps(tokenProps, identifier, options) { const props = options ? { ...options } : {}; if (typeof identifier === "string") { if (Id64.isId64(identifier)) props.id = identifier; else props.federationGuid = identifier; } else { props.code = Code.fromJSON(identifier); } const iModelDb = await getIModelForRpc(tokenProps); return iModelDb.elements.tryGetElementProps(props); } async getGeometrySummary(tokenProps, request) { const iModel = await getIModelForRpc(tokenProps); return generateGeometrySummaries(request, iModel); } async queryElementProps(tokenProps, params) { const ids = await this.queryEntityIds(tokenProps, params); const res = this.getElementProps(tokenProps, [...ids]); return res; } async queryEntityIds(tokenProps, params) { const iModelDb = await getIModelForRpc(tokenProps); const res = iModelDb.queryEntityIds(params); return [...res]; } async getClassHierarchy(tokenProps, classFullName) { const iModelDb = await getIModelForRpc(tokenProps); const classArray = []; while (true) { // eslint-disable-next-line @typescript-eslint/no-deprecated const classMetaData = iModelDb.getMetaData(classFullName); classArray.push(classFullName); if (!classMetaData.baseClasses || classMetaData.baseClasses.length === 0) break; classFullName = classMetaData.baseClasses[0]; } return classArray; } async getAllCodeSpecs(tokenProps) { const codeSpecs = []; const iModelDb = await getIModelForRpc(tokenProps); // eslint-disable-next-line @typescript-eslint/no-deprecated iModelDb.withPreparedStatement("SELECT ECInstanceId AS id, name, jsonProperties FROM BisCore.CodeSpec", (statement) => { for (const row of statement) codeSpecs.push({ id: row.id, name: row.name, jsonProperties: JSON.parse(row.jsonProperties) }); }); return codeSpecs; } async getViewStateData(tokenProps, viewDefinitionId, options) { const iModelDb = await getIModelForRpc(tokenProps); return iModelDb.views.getViewStateProps(viewDefinitionId, options); } async readFontJson(tokenProps) { const iModelDb = await getIModelForRpc(tokenProps); return iModelDb[_nativeDb].readFontMap(); } async requestSnap(tokenProps, sessionId, props) { const iModelDb = await getIModelForRpc(tokenProps); return iModelDb.requestSnap(sessionId, props); } async cancelSnap(tokenProps, sessionId) { const iModelDb = await getIModelForRpc(tokenProps); return iModelDb.cancelSnap(sessionId); } async getGeometryContainment(tokenProps, props) { const iModelDb = await getIModelForRpc(tokenProps); return iModelDb.getGeometryContainment(props); } async getMassProperties(tokenProps, props) { const iModelDb = await getIModelForRpc(tokenProps); return iModelDb.getMassProperties(props); } async getMassPropertiesPerCandidate(tokenProps, props) { const iModelDb = await getIModelForRpc(tokenProps); const getSingleCandidateMassProperties = async (candidate) => { try { const massPropResults = []; for (const op of props.operations) { const massProperties = await iModelDb.getMassProperties({ operation: op, candidates: [candidate] }); massPropResults.push(massProperties); } let singleCandidateResult = { status: BentleyStatus.ERROR, candidate }; // eslint-disable-line @typescript-eslint/no-deprecated if (massPropResults.some((r) => r.status !== BentleyStatus.ERROR)) { singleCandidateResult.status = BentleyStatus.SUCCESS; for (const r of massPropResults.filter((mpr) => mpr.status !== BentleyStatus.ERROR)) { singleCandidateResult = { ...singleCandidateResult, ...r }; } } return singleCandidateResult; } catch { return { status: BentleyStatus.ERROR, candidate }; } }; const promises = []; // eslint-disable-line @typescript-eslint/no-deprecated for (const candidate of CompressedId64Set.iterable(props.candidates)) { promises.push(getSingleCandidateMassProperties(candidate)); } return Promise.all(promises); } async getToolTipMessage(tokenProps, id) { const iModelDb = await getIModelForRpc(tokenProps); const el = iModelDb.elements.getElement(id); return (el === undefined) ? [] : el.getToolTipMessage(); } /** Send a view thumbnail to the frontend. This is a binary transfer with the metadata in a 16-byte prefix header. * @deprecated in 3.6.0 - might be removed in next major version. Use queryViewThumbnail instead */ async getViewThumbnail(tokenProps, viewId) { const iModelDb = await getIModelForRpc(tokenProps); const thumbnail = iModelDb.views.getThumbnail(viewId); if (undefined === thumbnail || 0 === thumbnail.image.length) throw new NoContentError(); const val = new Uint8Array(thumbnail.image.length + 16); // allocate a new buffer 16 bytes larger than the image size new Uint32Array(val.buffer, 0, 4).set([thumbnail.image.length, thumbnail.format === "jpeg" ? ImageSourceFormat.Jpeg : ImageSourceFormat.Png, thumbnail.width, thumbnail.height]); // Put the metadata in the first 16 bytes. val.set(thumbnail.image, 16); // put the image data at offset 16 after metadata return val; } async getDefaultViewId(tokenProps) { const iModelDb = await getIModelForRpc(tokenProps); const spec = { namespace: "dgn_View", name: "DefaultView" }; const blob = iModelDb.queryFilePropertyBlob(spec); if (undefined === blob || 8 !== blob.length) return Id64.invalid; const view = new Uint32Array(blob.buffer); return Id64.fromUint32Pair(view[0], view[1]); } async getSpatialCategoryId(tokenProps, categoryName) { const iModelDb = await getIModelForRpc(tokenProps); const dictionary = iModelDb.models.getModel(IModel.dictionaryId); return SpatialCategory.queryCategoryIdByName(iModelDb, dictionary.id, categoryName); } async getIModelCoordinatesFromGeoCoordinates(tokenProps, props) { const iModelDb = await getIModelForRpc(tokenProps); return iModelDb.getIModelCoordinatesFromGeoCoordinates(props); } async getGeoCoordinatesFromIModelCoordinates(tokenProps, props) { const iModelDb = await getIModelForRpc(tokenProps); return iModelDb.getGeoCoordinatesFromIModelCoordinates(props); } async queryTextureData(tokenProps, textureLoadProps) { const db = await getIModelForRpc(tokenProps); return db.queryTextureData(textureLoadProps); } async generateElementMeshes(tokenProps, props) { const db = await getIModelForRpc(tokenProps); return db[_nativeDb].generateElementMeshes(props); } /** @internal */ async callViewStore(tokenProps, version, forWrite, methodName, ...args) { if (!RpcInterface.isVersionCompatible(ViewStoreRpc.version, version)) throw new Error("ViewStoreRpc version mismatch"); const db = await getIModelForRpc(tokenProps); const viewStore = await db.views.accessViewStore({ accessLevel: forWrite ? "write" : "read" }); const access = viewStore[forWrite ? "writeLocker" : "reader"]; const func = access[methodName]; if (typeof func !== "function") throw new IModelError(IModelStatus.FunctionNotFound, `Illegal ViewStore RPC call "${methodName}"`); return func.call(access, ...args); } } //# sourceMappingURL=IModelReadRpcImpl.js.map