@rdfc/file-utils-processors-ts
Version:
File util functions to be used within the RDF-Connect ecosystem
195 lines (194 loc) • 7.06 kB
JavaScript
import path from "path";
import { memoryUsage } from "node:process";
import { access, readdir, readFile } from "fs/promises";
import { glob } from "glob";
import AdmZip from "adm-zip";
import { getLoggerFor } from "./utils/logUtil.js";
import * as zlib from "node:zlib";
import { promisify } from "node:util";
export async function globRead(globPattern, writer, wait = 0, closeOnEnd = true, binary = false) {
const logger = getLoggerFor("globRead");
const jsfiles = await glob(globPattern, {});
if (jsfiles.length === 0) {
logger.warn(`No files found for glob pattern '${globPattern}'. Double check the pattern if this is unexpected.`);
}
const files = await Promise.all(jsfiles.map((x) => {
logger.info(`Reading file '${x}' (from glob pattern '${globPattern}')`);
return readFile(x, binary ? {} : { encoding: "utf8" });
}));
let writerClosed = false;
writer.on("end", () => {
writerClosed = true;
});
// This is a source processor (i.e, the first processor in a pipeline),
// therefore we should wait until the rest of the pipeline is set
// to start pushing down data
return async () => {
for (const file of files) {
if (writerClosed) {
logger.info("Writer closed, so stopping pushing data");
break;
}
await writer.push(file);
await new Promise((res) => setTimeout(res, wait));
}
if (closeOnEnd) {
// Signal that all files were streamed
await writer.end();
}
};
}
export async function readFolder(folder, writer, maxMemory = 3, pause = 5000) {
const logger = getLoggerFor("readFolder");
const normalizedPath = path.normalize(folder);
// Check folder exists
await access(normalizedPath);
// Read folder content and iterate over each file
const fileNames = await readdir(normalizedPath, { recursive: true });
logger.info(`Reading these files: ${fileNames}`);
let writerClosed = false;
writer.on("end", () => {
writerClosed = true;
});
// This is a source processor (i.e, the first processor in a pipeline),
// therefore we should wait until the rest of the pipeline is set
// to start pushing down data
return async () => {
for (const fileName of fileNames) {
if (writerClosed) {
logger.info("Writer closed, so stopping pushing data");
break;
}
// Monitor process memory to avoid high-water memory crashes
logger.info(`[readFolder] processing '${fileName}'`);
if (memoryUsage().heapUsed > maxMemory * 1024 * 1024 * 1024) {
logger.warn(`[readFolder] Phew! too much data (used ${Math.round((memoryUsage().heapUsed / 1024 / 1024) * 100) / 100} Mb)...waiting for ${pause / 1000}s`);
await sleep(pause);
}
await writer.push((await readFile(path.join(normalizedPath, fileName))).toString());
}
// Signal that all files were streamed
await writer.end();
};
}
function sleep(x) {
return new Promise(resolve => setTimeout(resolve, x));
}
export function substitute(reader, writer, source, replace, regexp = false) {
const logger = getLoggerFor("substitute");
const reg = regexp ? new RegExp(source) : source;
let writerClosed = false;
writer.on("end", async () => {
logger.info("Writer closed, so closing reader as well.");
writerClosed = true;
await reader.end();
});
reader.data(async (x) => {
logger.info(`Replacing '${source}' by '${replace}' on input text`);
await writer.push(x.replaceAll(reg, replace));
});
reader.on("end", async () => {
if (!writerClosed) {
await writer.end();
}
});
}
export function envsub(reader, writer) {
const logger = getLoggerFor("envsub");
const env = process.env;
let writerClosed = false;
writer.on("end", async () => {
logger.info("Writer closed, so closing reader as well.");
writerClosed = true;
await reader.end();
});
reader.data(async (x) => {
logger.info("Replacing environment variable on input text");
Object.keys(env).forEach(key => {
const v = env[key];
if (v) {
x = x.replaceAll(`\${${key}}`, v);
}
});
await writer.push(x);
});
reader.on("end", async () => {
if (!writerClosed) {
await writer.end();
}
});
}
export function getFileFromFolder(reader, folderPath, writer) {
const logger = getLoggerFor("getFileFromFolder");
let writerClosed = false;
writer.on("end", async () => {
logger.info("Writer closed, so closing reader as well.");
writerClosed = true;
await reader.end();
});
reader.data(async (name) => {
const filePath = path.join(path.resolve(folderPath), name);
logger.info(`Reading file at '${filePath}'`);
const file = await readFile(filePath, "utf8");
await writer.push(file);
});
reader.on("end", async () => {
if (!writerClosed) {
await writer.end();
}
});
}
export function unzipFile(reader, writer, outputAsBuffer = false) {
const logger = getLoggerFor("unzipFile");
let writerClosed = false;
writer.on("end", async () => {
logger.info("Writer closed, so closing reader as well.");
writerClosed = true;
await reader.end();
});
reader.data(async (data) => {
try {
const adm = new AdmZip(data);
for (const entry of adm.getEntries()) {
logger.info(`Unzipping received file '${entry.entryName}'`);
await writer.push(outputAsBuffer ? entry.getData() : entry.getData().toString());
}
}
catch (ex) {
logger.error("Ignoring invalid ZIP file received");
logger.debug(ex);
}
});
reader.on("end", async () => {
if (!writerClosed) {
await writer.end();
}
});
}
export function gunzipFile(reader, writer, outputAsBuffer = false) {
const logger = getLoggerFor("gunzipFile");
let writerClosed = false;
writer.on("end", async () => {
logger.info("Writer closed, so closing reader as well.");
writerClosed = true;
await reader.end();
});
const gunzip = promisify(zlib.gunzip);
reader.data(async (data) => {
try {
const buffer = await gunzip(data);
logger.info("Unzipping received file");
await writer.push(outputAsBuffer ? buffer : buffer.toString());
}
catch (ex) {
logger.error("Ignoring invalid GZIP file received");
console.log(ex);
}
});
reader.on("end", async () => {
console.log("reader ended");
if (!writerClosed) {
await writer.end();
}
});
}