UNPKG

@huggingface/transformers

Version:

State-of-the-art Machine Learning for the web. Run 🤗 Transformers directly in your browser, with no need for a server!

129 lines (110 loc) • 4.53 kB
import fs from 'node:fs'; import path from 'node:path'; import { FileResponse } from '../hub/FileResponse.js'; import { Random } from '../random.js'; import { apis } from '../../env.js'; // Create a dedicated random instance for generating unique temporary file names const rng = new Random(); /** * File system cache implementation that implements the CacheInterface. * Provides `match` and `put` methods compatible with the Web Cache API. */ export class FileCache { /** * Instantiate a `FileCache` object. * @param {string} path */ constructor(path) { this.path = path; } /** * Checks whether the given request is in the cache. * @param {string} request * @returns {Promise<FileResponse | undefined>} */ async match(request) { let filePath = path.join(this.path, request); let file = new FileResponse(filePath); if (file.exists) { return file; } else { return undefined; } } /** * Adds the given response to the cache. * @param {string} request * @param {Response} response * @param {(data: {progress: number, loaded: number, total: number}) => void} [progress_callback] Optional. * The function to call with progress updates * @returns {Promise<void>} */ async put(request, response, progress_callback = undefined) { const filePath = path.join(this.path, request); // Include both PID and a random suffix so that concurrent put() call within the same process // (e.g., multiple pipelines loading the same file in parallel) each get their own temp file // and don't corrupt each other's writes. const id = apis.IS_PROCESS_AVAILABLE ? process.pid : Date.now(); const randomSuffix = rng._int32().toString(36); const tmpPath = filePath + `.tmp.${id}.${randomSuffix}`; try { const contentLength = response.headers.get('Content-Length'); const total = parseInt(contentLength ?? '0'); let loaded = 0; await fs.promises.mkdir(path.dirname(filePath), { recursive: true }); const fileStream = fs.createWriteStream(tmpPath); const reader = response.body.getReader(); while (true) { const { done, value } = await reader.read(); if (done) { break; } await new Promise((resolve, reject) => { fileStream.write(value, (err) => { if (err) { reject(err); return; } resolve(); }); }); loaded += value.length; const progress = total ? (loaded / total) * 100 : 0; progress_callback?.({ progress, loaded, total }); } await new Promise((resolve, reject) => { fileStream.close((err) => (err ? reject(err) : resolve())); }); // Atomically move the completed temp file to the final path so that // concurrent readers (other processes or other in-process calls) // never observe a partially-written file. await fs.promises.rename(tmpPath, filePath); } catch (error) { // Clean up the temp file if an error occurred during download try { await fs.promises.unlink(tmpPath); } catch {} throw error; } } /** * Deletes the cache entry for the given request. * @param {string} request * @returns {Promise<boolean>} A Promise that resolves to `true` if the cache entry was deleted, `false` otherwise. */ async delete(request) { let filePath = path.join(this.path, request); try { await fs.promises.unlink(filePath); return true; } catch (error) { // File doesn't exist or couldn't be deleted return false; } } // TODO add the rest? // addAll(requests: RequestInfo[]): Promise<void>; // keys(request?: RequestInfo | URL, options?: CacheQueryOptions): Promise<ReadonlyArray<Request>>; // match(request: RequestInfo | URL, options?: CacheQueryOptions): Promise<Response | undefined>; // matchAll(request?: RequestInfo | URL, options?: CacheQueryOptions): Promise<ReadonlyArray<Response>>; }