blob: ba2ffece13373cf3f0ef4c5d0230b4df9bed1109 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
const DefaultBackend = require("./DefaultBackend.js");
const Stat = require("./Stat.js");
const path = require("./path.js");
function cleanParamsFilepathOpts(filepath, opts, ...rest) {
// normalize paths
filepath = path.normalize(filepath);
// strip out callbacks
if (typeof opts === "undefined" || typeof opts === "function") {
opts = {};
}
// expand string options to encoding options
if (typeof opts === "string") {
opts = {
encoding: opts,
};
}
return [filepath, opts, ...rest];
}
function cleanParamsFilepathsOpts(filepaths, opts, ...rest) {
// normalize paths
for (let i = 0; i < filepaths.length; i++) {
filepaths[i] = path.normalize(filepaths[i]);
}
// strip out callbacks
if (typeof opts === "undefined" || typeof opts === "function") {
opts = {};
}
// expand string options to encoding options
if (typeof opts === "string") {
opts = {
encoding: opts,
};
}
return [filepaths, opts, ...rest];
}
function cleanParamsFilepathDataOpts(filepath, data, opts, ...rest) {
// normalize paths
filepath = path.normalize(filepath);
// strip out callbacks
if (typeof opts === "undefined" || typeof opts === "function") {
opts = {};
}
// expand string options to encoding options
if (typeof opts === "string") {
opts = {
encoding: opts,
};
}
return [filepath, data, opts, ...rest];
}
function cleanParamsFilesOpts(files, opts, ...rest) {
// normalize paths
for (const file of files) {
file[0] = path.normalize(file[0]);
}
// strip out callbacks
if (typeof opts === "undefined" || typeof opts === "function") {
opts = {};
}
// expand string options to encoding options
if (typeof opts === "string") {
opts = {
encoding: opts,
};
}
return [files, opts, ...rest];
}
function cleanParamsFilepathFilepath(oldFilepath, newFilepath, ...rest) {
// normalize paths
return [path.normalize(oldFilepath), path.normalize(newFilepath), ...rest];
}
module.exports = class PromisifiedKieSandboxFs {
constructor(name, options = {}) {
this.init = this.init.bind(this);
this.readFile = this._wrap(this.readFile, cleanParamsFilepathOpts, false);
this.readFileBulk = this._wrap(this.readFileBulk, cleanParamsFilepathsOpts, false);
this.writeFile = this._wrap(this.writeFile, cleanParamsFilepathDataOpts, true);
this.writeFileBulk = this._wrap(this.writeFileBulk, cleanParamsFilesOpts, true);
this.unlink = this._wrap(this.unlink, cleanParamsFilepathOpts, true);
this.unlinkBulk = this._wrap(this.unlinkBulk, cleanParamsFilepathsOpts, true);
this.readdir = this._wrap(this.readdir, cleanParamsFilepathOpts, false);
this.mkdir = this._wrap(this.mkdir, cleanParamsFilepathOpts, true);
this.rmdir = this._wrap(this.rmdir, cleanParamsFilepathOpts, true);
this.rename = this._wrap(this.rename, cleanParamsFilepathFilepath, true);
this.stat = this._wrap(this.stat, cleanParamsFilepathOpts, false);
this.lstat = this._wrap(this.lstat, cleanParamsFilepathOpts, false);
this.readlink = this._wrap(this.readlink, cleanParamsFilepathOpts, false);
this.symlink = this._wrap(this.symlink, cleanParamsFilepathFilepath, true);
this.backFile = this._wrap(this.backFile, cleanParamsFilepathOpts, true);
this.du = this._wrap(this.du, cleanParamsFilepathOpts, false);
this._deactivationPromise = null;
this._deactivationTimeout = null;
this._activationPromise = null;
this._operations = new Set();
if (name) {
this.init(name, options);
}
}
async init(...args) {
if (this._initPromiseResolve) await this._initPromise;
this._initPromise = this._init(...args);
return this._initPromise;
}
async _init(name, options = {}) {
await this._gracefulShutdown();
if (this._activationPromise) await this._deactivate();
if (this._backend && this._backend.destroy) {
await this._backend.destroy();
}
this._backend = options.backend || new DefaultBackend();
if (this._backend.init) {
await this._backend.init(name, options);
}
if (this._initPromiseResolve) {
this._initPromiseResolve();
this._initPromiseResolve = null;
}
// The next comment starting with the "fs is initially activated when constructed"?
// That can create contention for the mutex if two threads try to init at the same time
// so I've added an option to disable that behavior.
if (!options.defer) {
// The fs is initially activated when constructed (in order to wipe/save the superblock)
// This is not awaited, because that would create a cycle.
// FIXME: Tiago: commented to fix "a custom backend" test
// this.stat("/");
}
}
async _gracefulShutdown() {
if (this._operations.size > 0) {
this._isShuttingDown = true;
await new Promise((resolve) => (this._gracefulShutdownResolve = resolve));
this._isShuttingDown = false;
this._gracefulShutdownResolve = null;
}
}
_wrap(fn, paramCleaner, mutating) {
return async (...args) => {
args = paramCleaner(...args);
let op = {
name: fn.name,
args,
};
this._operations.add(op);
try {
await this._activate();
return await fn.apply(this, args);
} finally {
this._operations.delete(op);
if (mutating) this._backend.saveSuperblock(); // this is debounced
if (this._operations.size === 0) {
if (!this._deactivationTimeout) clearTimeout(this._deactivationTimeout);
this._deactivationTimeout = setTimeout(this._deactivate.bind(this), 500);
}
}
};
}
async _activate() {
if (!this._initPromise)
console.warn(new Error(`Attempted to use KieSandboxFs ${this._name} before it was initialized.`));
await this._initPromise;
if (this._deactivationTimeout) {
clearTimeout(this._deactivationTimeout);
this._deactivationTimeout = null;
}
if (this._deactivationPromise) await this._deactivationPromise;
this._deactivationPromise = null;
if (!this._activationPromise) {
this._activationPromise = this._backend.activate ? this._backend.activate() : Promise.resolve();
}
await this._activationPromise;
}
async deactivate() {
await this._deactivate();
}
async _deactivate() {
if (this._activationPromise) await this._activationPromise;
if (!this._deactivationPromise) {
this._deactivationPromise = this._backend.deactivate ? this._backend.deactivate() : Promise.resolve();
}
this._activationPromise = null;
if (this._gracefulShutdownResolve) this._gracefulShutdownResolve();
return this._deactivationPromise;
}
async readFile(filepath, opts) {
return this._backend.readFile(filepath, opts);
}
async readFileBulk(filepaths, opts) {
return this._backend.readFileBulk(filepaths, opts);
}
async writeFile(filepath, data, opts) {
await this._backend.writeFile(filepath, data, opts);
return null;
}
async writeFileBulk(files, opts) {
await this._backend.writeFileBulk(files, opts);
return null;
}
async unlink(filepath, opts) {
await this._backend.unlink(filepath, opts);
return null;
}
async unlinkBulk(filepath, opts) {
await this._backend.unlinkBulk(filepath, opts);
return null;
}
async readdir(filepath, opts) {
return this._backend.readdir(filepath, opts);
}
async mkdir(filepath, opts) {
await this._backend.mkdir(filepath, opts);
return null;
}
async rmdir(filepath, opts) {
await this._backend.rmdir(filepath, opts);
return null;
}
async rename(oldFilepath, newFilepath) {
await this._backend.rename(oldFilepath, newFilepath);
return null;
}
async stat(filepath, opts) {
const data = await this._backend.stat(filepath, opts);
return new Stat(data);
}
async lstat(filepath, opts) {
const data = await this._backend.lstat(filepath, opts);
return new Stat(data);
}
async readlink(filepath, opts) {
return this._backend.readlink(filepath, opts);
}
async symlink(target, filepath) {
await this._backend.symlink(target, filepath);
return null;
}
async backFile(filepath, opts) {
await this._backend.backFile(filepath, opts);
return null;
}
async du(filepath) {
return this._backend.du(filepath);
}
};