src/desktop/ci/job_log_cache.ts (92 lines of code) (raw):

import * as vscode from 'vscode'; import { JobLogRefresher } from './job_log_refresher'; export type JobTraceSection = { startLine: number; startTime: number; endLine?: number; endTime?: number; }; export type CacheItem = { repositoryRoot?: string; rawTrace: string; filtered?: string; decorations?: Map<string, vscode.DecorationOptions[]>; sections?: Map<string, JobTraceSection>; eTag: string | null; }; // Additional implementation-specific properties type InnerCacheItem = CacheItem & { lastOpened: number; refresher?: JobLogRefresher; }; export class JobLogCache { #onDidChangeEmitter = new vscode.EventEmitter<number>(); onDidJobChange = this.#onDidChangeEmitter.event; #storage: Record<number, InnerCacheItem> = {}; touch(jobId: number) { if (this.#storage[jobId]) { this.#storage[jobId].lastOpened = new Date().getTime(); } } get(jobId: number): CacheItem | undefined { return this.#storage[jobId]; } set(jobId: number, rawTrace: string) { const exists = Boolean(this.#storage[jobId]); this.#storage[jobId]?.refresher?.dispose(); this.#storage[jobId] = { rawTrace, eTag: null, lastOpened: this.#storage[jobId]?.lastOpened ?? new Date().getTime(), }; if (exists) this.#onDidChangeEmitter.fire(jobId); } setForRunning(repositoryRoot: string, jobId: number, rawTrace: string, eTag: string | null) { const exists = Boolean(this.#storage[jobId]); this.#storage[jobId] = { repositoryRoot, rawTrace, lastOpened: this.#storage[jobId]?.lastOpened ?? new Date().getTime(), eTag, refresher: this.#storage[jobId]?.refresher, }; if (exists) this.#onDidChangeEmitter.fire(jobId); } startRefreshing(jobId: number) { const item = this.#storage[jobId]; if (!item || item.eTag === null || item.refresher) return; item.refresher = new JobLogRefresher(this, jobId); } stopRefreshing(jobId: number) { this.#storage[jobId]?.refresher?.dispose(); delete this.#storage[jobId]?.refresher; } addDecorations( jobId: number, sections: Map<string, JobTraceSection>, decorations: Map<string, vscode.DecorationOptions[]>, filtered: string, ) { this.#storage[jobId] = { ...this.#storage[jobId], sections, decorations, filtered, }; } async delete(jobId: number) { if (!this.#storage[jobId]) return; // When a document changes its language, VS Code emits a close and open event in succession. // Delay the removal of the cache entry, and abort if the document was accessed during the timeout. const { lastOpened } = this.#storage[jobId]; await new Promise<void>(accept => { setTimeout(accept, 2000); }); if (this.#storage[jobId]?.lastOpened === lastOpened) { this.#storage[jobId]?.refresher?.dispose(); // eslint-disable-next-line @typescript-eslint/no-dynamic-delete delete this.#storage[jobId]; } } clearAll() { Object.values(this.#storage).forEach(v => v.refresher?.dispose()); this.#storage = {}; } } export const jobLogCache = new JobLogCache();