Path: blob/main/extensions/json-language-features/client/src/node/schemaCache.ts
3322 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { promises as fs } from 'fs';6import * as path from 'path';7import { createHash } from 'crypto';8import { Memento } from 'vscode';910interface CacheEntry {11etag: string;12fileName: string;13updateTime: number;14}1516interface CacheInfo {17[schemaUri: string]: CacheEntry;18}1920const MEMENTO_KEY = 'json-schema-cache';2122export class JSONSchemaCache {23private cacheInfo: CacheInfo;2425constructor(private readonly schemaCacheLocation: string, private readonly globalState: Memento) {26const infos = globalState.get<CacheInfo>(MEMENTO_KEY, {}) as CacheInfo;27const validated: CacheInfo = {};28for (const schemaUri in infos) {29const { etag, fileName, updateTime } = infos[schemaUri];30if (typeof etag === 'string' && typeof fileName === 'string' && typeof updateTime === 'number') {31validated[schemaUri] = { etag, fileName, updateTime };32}33}34this.cacheInfo = validated;35}3637getETag(schemaUri: string): string | undefined {38return this.cacheInfo[schemaUri]?.etag;39}4041getLastUpdatedInHours(schemaUri: string): number | undefined {42const updateTime = this.cacheInfo[schemaUri]?.updateTime;43if (updateTime !== undefined) {44return (new Date().getTime() - updateTime) / 1000 / 60 / 60;45}46return undefined;47}4849async putSchema(schemaUri: string, etag: string, schemaContent: string): Promise<void> {50try {51const fileName = getCacheFileName(schemaUri);52await fs.writeFile(path.join(this.schemaCacheLocation, fileName), schemaContent);53const entry: CacheEntry = { etag, fileName, updateTime: new Date().getTime() };54this.cacheInfo[schemaUri] = entry;55} catch (e) {56delete this.cacheInfo[schemaUri];57} finally {58await this.updateMemento();59}60}6162async getSchemaIfUpdatedSince(schemaUri: string, expirationDurationInHours: number): Promise<string | undefined> {63const lastUpdatedInHours = this.getLastUpdatedInHours(schemaUri);64if (lastUpdatedInHours !== undefined && (lastUpdatedInHours < expirationDurationInHours)) {65return this.loadSchemaFile(schemaUri, this.cacheInfo[schemaUri], false);66}67return undefined;68}6970async getSchema(schemaUri: string, etag: string, etagValid: boolean): Promise<string | undefined> {71const cacheEntry = this.cacheInfo[schemaUri];72if (cacheEntry) {73if (cacheEntry.etag === etag) {74return this.loadSchemaFile(schemaUri, cacheEntry, etagValid);75} else {76this.deleteSchemaFile(schemaUri, cacheEntry);77}78}79return undefined;80}8182private async loadSchemaFile(schemaUri: string, cacheEntry: CacheEntry, isUpdated: boolean): Promise<string | undefined> {83const cacheLocation = path.join(this.schemaCacheLocation, cacheEntry.fileName);84try {85const content = (await fs.readFile(cacheLocation)).toString();86if (isUpdated) {87cacheEntry.updateTime = new Date().getTime();88}89return content;90} catch (e) {91delete this.cacheInfo[schemaUri];92return undefined;93} finally {94await this.updateMemento();95}96}9798private async deleteSchemaFile(schemaUri: string, cacheEntry: CacheEntry): Promise<void> {99const cacheLocation = path.join(this.schemaCacheLocation, cacheEntry.fileName);100delete this.cacheInfo[schemaUri];101await this.updateMemento();102try {103await fs.rm(cacheLocation);104} catch (e) {105// ignore106}107}108109110// for debugging111public getCacheInfo() {112return this.cacheInfo;113}114115private async updateMemento() {116try {117await this.globalState.update(MEMENTO_KEY, this.cacheInfo);118} catch (e) {119// ignore120}121}122123public async clearCache(): Promise<string[]> {124const uris = Object.keys(this.cacheInfo);125try {126const files = await fs.readdir(this.schemaCacheLocation);127for (const file of files) {128try {129await fs.unlink(path.join(this.schemaCacheLocation, file));130} catch (_e) {131// ignore132}133}134} catch (e) {135// ignore136} finally {137138this.cacheInfo = {};139await this.updateMemento();140}141return uris;142}143}144function getCacheFileName(uri: string): string {145return `${createHash('sha256').update(uri).digest('hex')}.schema.json`;146}147148149