import { isNonEmptyString, isString } from '@sindresorhus/is'; import { GlobalConfig } from '../../../../config/global.ts'; import { logger } from '../../../../logger/index.ts'; import { compressToBase64, decompressFromBase64 } from '../../../compress.ts'; import { hash } from '../../../hash.ts'; import { safeStringify } from '../../../stringify.ts'; import { CACHE_REVISION } from '../common.ts'; import { cleanupHttpCache } from '../http-cache.ts'; import { RepoCacheRecord } from '../schema.ts'; import type { RepoCache, RepoCacheData } from '../types.ts'; export abstract class RepoCacheBase implements RepoCache { protected platform = GlobalConfig.get('platform'); private oldHash: string | null = null; private data: RepoCacheData = {}; protected readonly repository: string; protected readonly fingerprint: string; protected constructor(repository: string, fingerprint: string) { this.repository = repository; this.fingerprint = fingerprint; } protected abstract read(): Promise; protected abstract write(data: RepoCacheRecord): Promise; private static parseData(input: string): RepoCacheData { const data: RepoCacheData = JSON.parse(input); // istanbul ignore next if (data.branches) { for (const branch of data.branches) { if (branch.branchFingerprint) { branch.commitFingerprint = branch.branchFingerprint; delete branch.branchFingerprint; } } } return data; } private async restore(oldCache: RepoCacheRecord): Promise { if (oldCache.fingerprint !== this.fingerprint) { logger.debug('Repository cache fingerprint is invalid'); return; } const jsonStr = await decompressFromBase64(oldCache.payload); this.data = RepoCacheBase.parseData(jsonStr); this.oldHash = oldCache.hash; } async load(): Promise { try { const oldCache = await this.read(); if (!isString(oldCache)) { logger.debug( `RepoCacheBase.load() - expecting data of type 'string' received '${typeof oldCache}' instead - skipping`, ); return; } if (!isNonEmptyString(oldCache)) { logger.debug('RepoCacheBase.load() - cache file is empty - skipping'); return; } const cacheV13 = RepoCacheRecord.safeParse(oldCache); if (cacheV13.success) { await this.restore(cacheV13.data); logger.debug('Repository cache is restored from revision 13'); return; } logger.warn({ err: cacheV13.error }, 'Repository cache is invalid'); } catch (err) /* istanbul ignore next: not easily testable */ { logger.debug({ err }, 'Error reading repository cache'); } } async save(): Promise { cleanupHttpCache(this.data); const jsonStr = safeStringify(this.data); const hashedJsonStr = hash(jsonStr); if (hashedJsonStr === this.oldHash) { return; } const revision = CACHE_REVISION; const repository = this.repository; const fingerprint = this.fingerprint; const payload = await compressToBase64(jsonStr); await this.write({ revision, repository, fingerprint, payload, hash: hashedJsonStr, }); } getData(): RepoCacheData { return this.data; } isModified(): boolean | undefined { if (!this.oldHash) { return undefined; } const jsonStr = safeStringify(this.data); return hash(jsonStr) !== this.oldHash; } }