UNPKG

@itwin/core-backend

Version:
162 lines • 7.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.TileStorage = void 0; /*--------------------------------------------------------------------------------------------- * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ const zlib_1 = require("zlib"); const util_1 = require("util"); const core_common_1 = require("@itwin/core-common"); const core_bentley_1 = require("@itwin/core-bentley"); const BackendLoggerCategory_1 = require("./BackendLoggerCategory"); const IModelHost_1 = require("./IModelHost"); /** * Facilitates interaction with cloud tile cache. * @beta */ class TileStorage { /** * Allows using the underlying `ServerStorage` API directly. * @see https://github.com/iTwin/object-storage/ */ storage; constructor(storage) { this.storage = storage; } _initializedIModels = new Set(); /** * Ensures any required cloud storage resources for a specific iModel are ready to use. */ async initialize(iModelId) { if (this._initializedIModels.has(iModelId)) return; if (!(await this.storage.baseDirectoryExists({ baseDirectory: iModelId }))) { try { await this.storage.createBaseDirectory({ baseDirectory: iModelId }); } catch (e) { // Ignore 409 errors. This is what Azure blob storage returns when the container already exists. // Usually this means multiple backends tried to initialize tile storage at the same time. if (e.statusCode !== 409) throw e; } } this._initializedIModels.add(iModelId); } /** * Returns config that can be used by frontends to download tiles * @param iModelId Id of the iModel * @param expiresInSeconds Optional number of seconds until the download URL expires. Defaults to expiring exactly at midnight of next Sunday to enable persistent client-side caching. * It is recommended to set this to a shorter period when using S3-compatible storage - an exact expiry date cannot be ensured due to limitations in their API. * @see [TileStorage]($frontend) */ async getDownloadConfig(iModelId, expiresInSeconds) { try { if (expiresInSeconds !== undefined) return await this.storage.getDownloadConfig({ baseDirectory: iModelId }, { expiresInSeconds }); const expiresOn = new Date(); expiresOn.setDate(expiresOn.getDate() + (7 - expiresOn.getDay())); // next Sunday expiresOn.setHours(0, 0, 0, 0); // exactly at midnight return await this.storage.getDownloadConfig({ baseDirectory: iModelId }, { expiresOn }); } catch (err) { this.logException("Failed to get download config", err); throw err; } } /** * Uploads a tile to the cloud cache. */ async uploadTile(iModelId, changesetId, treeId, contentId, content, guid, metadata) { try { await this.storage.upload((0, core_common_1.getTileObjectReference)(iModelId, changesetId, treeId, contentId, guid), Buffer.from(IModelHost_1.IModelHost.compressCachedTiles ? await (0, util_1.promisify)(zlib_1.gzip)(content.buffer) : content.buffer), metadata, IModelHost_1.IModelHost.compressCachedTiles ? { contentEncoding: "gzip" } : undefined); } catch (err) { this.logException("Failed to upload tile", err); throw err; } } /** * Downloads a tile from the cloud cache. */ async downloadTile(iModelId, changesetId, treeId, contentId, guid) { try { const buffer = await this.storage.download((0, core_common_1.getTileObjectReference)(iModelId, changesetId, treeId, contentId, guid), "buffer"); return IModelHost_1.IModelHost.compressCachedTiles ? await (0, util_1.promisify)(zlib_1.gunzip)(buffer) : buffer; } catch (err) { this.logException("Failed to download tile", err); throw err; } } /** * Returns an async iterator of all tiles that are found in the cloud cache. */ async *getCachedTilesGenerator(iModelId) { const iterator = this.getCachedTilePages(iModelId); for await (const page of iterator) { for (const tile of page) { yield tile; } } } async *getCachedTilePages(iModelId) { const iterator = this.storage.getListObjectsPagedIterator({ baseDirectory: iModelId }, 500); let prevPage; do { // initiate loading the next page const page = iterator.next(); // process results from the previous page if (prevPage) yield this.convertPage(prevPage.value); // finish loading the next page prevPage = await page; } while (!prevPage.done); } convertPage(page) { return page .map((objectReference) => ({ parts: objectReference.relativeDirectory?.split("/") ?? [""], objectName: objectReference.objectName, })) .filter(({ parts, objectName }) => { if (parts[0] !== "tiles") return false; if (parts.length !== 3) { core_bentley_1.Logger.logWarning(BackendLoggerCategory_1.BackendLoggerCategory.IModelTileStorage, "Malformed tile id found in tile cache: {tileId}", { tileId: [...parts, objectName].join("/") }); return false; } return true; }).map(({ parts, objectName }) => { // relativeDirectory = tiles/<treeId>/<guid> // objectName = <contentId> return { treeId: parts[1], contentId: objectName, guid: parts[2], }; }); } /** * Returns a list of all tiles that are found in the cloud cache. */ async getCachedTiles(iModelId) { const results = []; for await (const page of this.getCachedTilePages(iModelId)) { results.push(...page); } return results; } /** * Returns a boolean indicating whether a tile exists in the cloud cache */ async isTileCached(iModelId, changesetId, treeId, contentId, guid) { return this.storage.objectExists((0, core_common_1.getTileObjectReference)(iModelId, changesetId, treeId, contentId, guid)); } logException(message, err) { core_bentley_1.Logger.logException(BackendLoggerCategory_1.BackendLoggerCategory.IModelTileStorage, err, (category, msg, errorMetadata) => core_bentley_1.Logger.logError(category, `${message}: {errorMessage}`, { ...errorMetadata, errorMessage: msg })); } } exports.TileStorage = TileStorage; //# sourceMappingURL=TileStorage.js.map