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
JavaScript
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