@itwin/core-backend
Version:
iTwin.js backend components
162 lines • 7.1 kB
JavaScript
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
;