hfs
Version:
HTTP File Server
103 lines (102 loc) • 5.08 kB
JavaScript
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.DESCRIPT_ION = void 0;
exports.usingDescriptIon = usingDescriptIon;
exports.getCommentFor = getCommentFor;
exports.setCommentFor = setCommentFor;
exports.areCommentsEnabled = areCommentsEnabled;
const config_1 = require("./config");
const path_1 = require("path");
const cross_1 = require("./cross");
const util_files_1 = require("./util-files");
const misc_1 = require("./misc");
const lodash_1 = __importDefault(require("lodash"));
const iconv_lite_1 = __importDefault(require("iconv-lite"));
const promises_1 = require("node:fs/promises");
exports.DESCRIPT_ION = 'descript.ion';
const commentsStorage = (0, config_1.defineConfig)(cross_1.CFG.comments_storage, '');
(0, config_1.defineConfig)('descript_ion', true, (v, more) => {
var _a;
if (!v && ((_a = more.version) === null || _a === void 0 ? void 0 : _a.olderThan('0.57.0-alpha1')))
commentsStorage.set('attr');
});
const descriptIonEncoding = (0, config_1.defineConfig)('descript_ion_encoding', 'utf8');
function readFromDescriptIon(path) {
return usingDescriptIon() && readDescriptIon((0, path_1.dirname)(path)).then(x => x.get((0, path_1.basename)(path)), () => undefined);
}
function usingDescriptIon() {
return ['', 'attr+ion'].includes(commentsStorage.get());
}
const COMMENT_ATTR = 'comment';
async function getCommentFor(path) {
return !path ? undefined : Promise.all([
commentsStorage.get() ? (0, misc_1.loadFileAttr)(path, COMMENT_ATTR) : undefined,
readFromDescriptIon(path)
]).then(([fromAttr, fromIon]) => fromAttr || fromIon);
}
async function setCommentFor(path, comment) {
if (commentsStorage.get()) {
await (0, misc_1.storeFileAttr)(path, COMMENT_ATTR, comment || undefined);
return setCommentDescriptIon(path, '');
}
return setCommentDescriptIon(path, comment); // should we also remove from file-attr? not sure, but for the time we won't because #1 storeFileAttr is not really deleting, and we would store a lot of empty attributes, #2 more people will switch from descript.ion to attr (because introduced later) than the opposite
}
const setCommentDescriptIon = (0, misc_1.singleWorkerFromBatchWorker)(async (jobs) => {
const byFolder = lodash_1.default.groupBy(jobs, job => (0, path_1.dirname)(job[0]));
return Promise.allSettled(lodash_1.default.map(byFolder, async (jobs, folder) => {
const comments = await readDescriptIon(folder).catch(() => new Map());
for (const [path, comment] of jobs) {
const file = path.slice(folder.length + 1);
if (!comment)
comments.delete(file);
else
comments.set(file, comment);
}
const path = (0, path_1.join)(folder, exports.DESCRIPT_ION);
if (!comments.size)
return (0, promises_1.unlink)(path);
// encode comments in descript.ion format
const ws = await (0, util_files_1.safeWriteStream)(path);
comments.forEach((comment, filename) => {
const multiline = comment.includes('\n');
const line = (filename.includes(' ') ? `"${filename}"` : filename)
+ ' ' + (multiline ? comment.replaceAll('\n', '\\n') : comment);
ws.write(iconv_lite_1.default.encode(line, descriptIonEncoding.get()));
if (multiline)
ws.write(MULTILINE_SUFFIX, 'binary');
ws.write('\n');
});
await new Promise(res => ws.end(res));
}));
});
function areCommentsEnabled() {
return true; // true since we introduced comments in file-attr
}
const MULTILINE_SUFFIX = Buffer.from([4, 0xC2]);
function readDescriptIon(path) {
// decoding could also be done with native TextDecoder.decode, but we need iconv for the encoding anyway
return (0, util_files_1.parseFileContent)((0, path_1.join)(path, exports.DESCRIPT_ION), raw => {
// for simplicity we "remove" the sequence MULTILINE_SUFFIX before iconv.decode messes it up
for (let i = 0; i < raw.length; i++)
if (raw[i] === MULTILINE_SUFFIX[0] && raw[i + 1] === MULTILINE_SUFFIX[1] && [undefined, 13, 10].includes(raw[i + 2]))
raw[i] = raw[i + 1] = 10;
const decoded = iconv_lite_1.default.decode(raw, descriptIonEncoding.get());
const ret = new Map(decoded.split('\n').map(line => {
const quoted = line[0] === '"' ? 1 : 0;
const i = quoted ? line.indexOf('"', 2) + 1 : line.indexOf(' ');
const fn = line.slice(quoted, i - quoted);
const comment = line.slice(i + 1).replaceAll('\\n', '\n');
return [fn, comment];
}));
ret.delete('');
return ret;
});
}
descriptIonEncoding.sub(() => {
for (const k of util_files_1.parseFileCache.keys())
if (k.endsWith(exports.DESCRIPT_ION))
util_files_1.parseFileCache.delete(k);
});
;