UNPKG

als-store

Version:

Library for streamlined file management and advanced data caching, featuring intelligent file searching, dynamic cache control, and flexible file operations

166 lines (149 loc) 5.22 kB
const os = require('os') const { stat, readFile, unlink, rename, outputFile, pathExists } = require('fs-extra') const { join } = require('path') const sizeof = require('als-sizeof') const { nameToParams, paramsToName } = require('./name-params') const isValidPath = require('als-path-validator') const isValidName = require('als-filename-validator') const slash = os.platform() === 'win32' ? '\\' : '/' const refSize = process.arch === 'x64' ? 8 : 4 const addCacheFns = require('./file-cache-fns') class File { static maxCacheSize = os.freemem() * 0.5 static stats = ['size', 'atimeMs', 'mtimeMs', 'birthtimeMs'] static currentCacheSize = 0 static cache = {} static map = new Set() static clearCache() { this.cache = {} this.map = new Set() } #value; #stats; #name; #params; #schema; constructor(path, name, schema) { const cacheDir = File.getDir(path,true).cache if(cacheDir[name]) { if (cacheDir[name].schema == null && schema) cacheDir[name].schema = schema return cacheDir[name] } else cacheDir[name] = this this.cacheDir = cacheDir this.#name = name this.path = path this.size = 0 this.schema = schema this.changed = false this.updateSize([name, path, this.size, this.#params,this.changed], undefined, refSize * 4) } get schema() { return this.#schema } set schema(schema) { if (!schema) return this.#params = schema.proxy(nameToParams(this.#name)) this.#schema = schema; } removeFromCache() { delete this.cacheDir[this.#name] File.map.delete(this) this.#value = null this.#stats = null File.currentCacheSize -= this.size + refSize } async getStats() { if (this.#stats) return if(pathExists(this.fullPath) === false) return const stats = await stat(this.fullPath) this.#stats = File.stats.map(k => stats[k]) this.updateSize(this.#stats) } async getValue() { if (this.#value) return let value = await readFile(this.fullPath) this.#value = value this.updateSize(value) } async save() { const { name, $name, buffer = Buffer.alloc(0), fullPath, $fullPath } = this const notExists = !(await pathExists(fullPath)) if (notExists) { // new file to save await outputFile($fullPath, buffer) this.#name = this.$name } else if (name !== $name) { // rename and save file await rename(fullPath, $fullPath) delete this.cacheDir[name] this.cacheDir[$name] = this this.updateSize($name, name) this.#name = this.$name } if (this.changed || notExists) { await outputFile($fullPath, buffer) this.changed = false this.#stats = null await this.getStats() } return this } async remove() { this.errors = [] try { await unlink(this.fullPath); this.removeFromCache() return true } catch (error) { this.errors.push(error) return false } } get name() { return this.#name } set name(newName) { if(isValidName(newName)) { const oldName = this.$name this.cacheDir[newName] = this.cacheDir[oldName] delete this.cacheDir[oldName] this.#name = newName this.updateSize(newName, oldName) } else throw new Error(`${newName} is not valid filename`) } get params() { return this.#params } get $name() { if (this.schema) return paramsToName(this.#params) // not saved name builded from params return this.#name } get fullPath() { const path = join(this.path, this.#name) if (!isValidPath(path)) throw new Error('fullPath is not valid') return path } get $fullPath() { const path = join(this.path, this.$name) if (!isValidPath(path)) throw new Error('$fullPath is not valid') return path } get buffer() { return this.#value } get value() { return this.#value ? this.#value.toString('utf-8') : null } set value(newValue) { this.#value = Buffer.isBuffer(newValue) ? newValue : Buffer.from(newValue) this.changed = true } get json() { return this.value ? JSON.parse(this.value) : null } set json(newValue) { this.value = JSON.stringify(newValue) } get stats() { if (!this.#stats) return {} const stats = {} File.stats.forEach((k, i) => stats[k] = this.#stats[i]) return stats } updateSize(toAdd, toRemove, add = 0) { const gap = sizeof(toAdd) - sizeof(toRemove) + add const expectedSize = File.currentCacheSize + gap while (expectedSize > File.maxCacheSize) { const next = File.map.values().next(); if (next.value) next.value.removeFromCache(); } this.size += gap File.currentCacheSize += gap if (gap > 0) { // LRU File.map.delete(this) File.map.add(this) } } } addCacheFns(File,refSize,slash) module.exports = File