import fs , { copyFileSync , mkdirSync } from 'fs' ; import constants from 'constants' ; import { removeDirectory } from './System' ; import path from 'path' ; import { noop } from '.' ; /**
 * A variant of the LRU cache where this cache stores mappings from keys to file paths. This throws out least recently
 * used items when adding a new file path to cache. Thrown out items are removed from cache and the file it pointed to
 * is deleted
 */ export default class LRUFileCache { public size = 0 ; public max : number ; private cache : Map < string , LRUFileCacheNode > = new Map ( ) ; public queueHead : LRUFileCacheNode = null ; public queueTail : LRUFileCacheNode = null ; public cachePath : string ; constructor ( max , cachePath ) { this . max = max ; this . cachePath = cachePath ; } param key param filepath async add ( key , filepath ) { let newfilesize = 0 ; key = key . replace ( /\//g , '_' ) ; try { const meta = fs . statSync ( filepath ) ; newfilesize = meta . size ; } catch ( err ) { if ( err . errno === - constants . ENOENT ) { throw new Error ( 'file does not exist' ) ; } } if ( newfilesize > this . max ) { throw new Error ( `file is at ${ filepath } too large, ${ newfilesize } > ${ this . max } allocated bytes` ) ; } let trimmedSize = this . size ; const removeFilePromises = [ ] ; while ( newfilesize + trimmedSize > this . max ) { const newtail = this . queueTail . prev ; trimmedSize -= this . queueTail . filesize ; removeFilePromises . push ( removeDirectory ( path . dirname ( this . getCachedFilePath ( this . queueTail . filepath , this . queueTail . key ) ) ) . catch ( noop ) ) ; this . cache . delete ( this . queueTail . key ) ; if ( newtail ) { newtail . next = null ; this . queueTail = newtail ; } } await Promise . all ( removeFilePromises ) ; const node = new LRUFileCacheNode ( filepath , newfilesize , key ) ; if ( this . cache . size === 0 ) { this . queueHead = node ; this . queueTail = node ; } else if ( this . cache . size === 1 ) { this . queueTail = this . queueHead ; this . queueHead = node ; this . queueHead . next = this . queueTail ; this . queueTail . prev = this . queueHead ; } else { node . next = this . queueHead ; this . queueHead . prev = node ; this . queueHead = node ; } this . cache . set ( key , node ) ; const cachedPath = this . getCachedFilePath ( filepath , key ) ; mkdirSync ( path . dirname ( cachedPath ) , { recursive : true , } ) ; copyFileSync ( filepath , cachedPath ) ; this . size = trimmedSize + newfilesize ; return cachedPath ; } private getCachedFilePath ( filepath , key ) { key = key . replace ( /\//g , '_' ) ; return path . join ( this . cachePath , key , path . basename ( filepath ) ) ; } has ( key ) { key = key . replace ( /\//g , '_' ) ; return this . cache . has ( key ) ; } param key get ( key ) { key = key . replace ( /\//g , '_' ) ; if ( this . has ( key ) ) { if ( this . cache . size !== 1 ) { const node = this . cache . get ( key ) ; if ( node . prev ) { node . prev . next = node . next ; } if ( node . next ) { node . next . prev = node . prev ; } if ( node === this . queueTail ) { this . queueTail = node . prev ; } node . next = this . queueHead ; this . queueHead . prev = node ; node . prev = null ; this . queueHead = node ; } return this . getCachedFilePath ( this . cache . get ( key ) . filepath , key ) ; } return undefined ; } } class LRUFileCacheNode { next : LRUFileCacheNode = null ; prev : LRUFileCacheNode = null ; constructor ( public filepath , public filesize , public key ) { } }