@zenfs/core
Version:
A filesystem, anywhere
129 lines (128 loc) • 3.71 kB
JavaScript
// SPDX-License-Identifier: LGPL-3.0-or-later
/* Note: this file is named file_index.ts because Typescript has special behavior regarding index.ts which can't be disabled. */
import { withErrno } from 'kerium';
import { sizeof } from 'memium';
import { isJSON, randomInt } from 'utilium';
import { basename, dirname } from '../path.js';
import { S_IFDIR, S_IFMT, size_max } from '../constants.js';
import { Inode } from './inode.js';
export const version = 1;
/**
* An index of file metadata
* @category Internals
* @internal
*/
export class Index extends Map {
maxSize = size_max;
/**
* Converts the index to JSON
*/
toJSON() {
return {
version,
maxSize: this.maxSize,
entries: Object.fromEntries([...this].map(([k, v]) => [k, v.toJSON()])),
};
}
/**
* Converts the index to a string
*/
toString() {
return JSON.stringify(this.toJSON());
}
/**
* Get the size in bytes of the index (including the size reported for each entry)
*/
get byteSize() {
let size = this.size * sizeof(Inode);
for (const entry of this.values())
size += entry.size;
return size;
}
usage() {
return {
totalSpace: this.maxSize,
freeSpace: this.maxSize - this.byteSize,
};
}
pathOf(id) {
for (const [path, inode] of this) {
if (inode.ino == id || inode.data == id)
return path;
}
}
getByID(id) {
return this.entryByID(id)?.inode;
}
entryByID(id) {
for (const [path, inode] of this) {
if (inode.ino == id || inode.data == id)
return { path, inode };
}
}
directoryEntries(path) {
const node = this.get(path);
if (!node)
throw withErrno('ENOENT');
if ((node.mode & S_IFMT) != S_IFDIR)
throw withErrno('ENOTDIR');
const entries = {};
for (const entry of this.keys()) {
if (dirname(entry) == path && entry != path) {
entries[basename(entry)] = this.get(entry).ino;
}
}
return entries;
}
/**
* Get the next available ID in the index
* @internal
*/
_alloc() {
return Math.max(...[...this.values()].flatMap(i => [i.ino, i.data])) + 1;
}
/**
* Gets a list of entries for each directory in the index.
* Use
*/
directories() {
const dirs = new Map();
for (const [path, node] of this) {
if ((node.mode & S_IFMT) != S_IFDIR)
continue;
const entries = {};
for (const entry of this.keys()) {
if (dirname(entry) == path && entry != path)
entries[basename(entry)] = this.get(entry).ino;
}
dirs.set(path, entries);
}
return dirs;
}
/**
* Loads the index from JSON data
*/
fromJSON(json) {
if (json.version != version)
throw withErrno('EINVAL', 'Index version mismatch');
this.clear();
for (const [path, node] of Object.entries(json.entries)) {
node.data ??= randomInt(1, size_max);
if (path == '/')
node.ino = 0;
this.set(path, new Inode(node));
}
return this;
}
/**
* Parses an index from a string
*/
static parse(data) {
if (!isJSON(data))
throw withErrno('EINVAL', 'Invalid JSON');
const json = JSON.parse(data);
const index = new Index();
index.fromJSON(json);
return index;
}
}