mirror of
https://github.com/The-Low-Code-Foundation/OpenNoodl.git
synced 2026-01-13 07:42:55 +01:00
Initial commit
Co-Authored-By: Eric Tuvesson <eric.tuvesson@gmail.com> Co-Authored-By: mikaeltellhed <2311083+mikaeltellhed@users.noreply.github.com> Co-Authored-By: kotte <14197736+mrtamagotchi@users.noreply.github.com> Co-Authored-By: Anders Larsson <64838990+anders-topp@users.noreply.github.com> Co-Authored-By: Johan <4934465+joolsus@users.noreply.github.com> Co-Authored-By: Tore Knudsen <18231882+torekndsn@users.noreply.github.com> Co-Authored-By: victoratndl <99176179+victoratndl@users.noreply.github.com>
This commit is contained in:
319
packages/noodl-platform-node/src/filesystem-node.ts
Normal file
319
packages/noodl-platform-node/src/filesystem-node.ts
Normal file
@@ -0,0 +1,319 @@
|
||||
import fs from 'fs';
|
||||
import nodePath from 'path';
|
||||
import fse, { mkdirp } from 'fs-extra';
|
||||
import JSZip from 'jszip';
|
||||
import { FileBlob, FileInfo, FileStat, IFileSystem, OpenDialogOptions } from '@noodl/platform';
|
||||
|
||||
export class FileSystemNode implements IFileSystem {
|
||||
resolve(...paths: string[]): string {
|
||||
return nodePath.resolve(...paths);
|
||||
}
|
||||
|
||||
join(...paths: string[]): string {
|
||||
return nodePath.join(...paths);
|
||||
}
|
||||
|
||||
exists(path: string): boolean {
|
||||
return fs.existsSync(path);
|
||||
}
|
||||
|
||||
dirname(path: string): string {
|
||||
return nodePath.dirname(path);
|
||||
}
|
||||
|
||||
basename(path: string): string {
|
||||
return nodePath.basename(path);
|
||||
}
|
||||
|
||||
file(path: string): FileStat {
|
||||
const stat = fs.lstatSync(path);
|
||||
return { size: stat.size };
|
||||
}
|
||||
|
||||
writeFile(path: string, blob: FileBlob): Promise<void> {
|
||||
if (typeof blob === 'string') {
|
||||
return fs.promises.writeFile(path, Buffer.from(blob));
|
||||
}
|
||||
|
||||
return fs.promises.writeFile(path, blob);
|
||||
}
|
||||
|
||||
async writeFileOverride(path: string, blob: FileBlob): Promise<void> {
|
||||
try {
|
||||
await this.removeFile(path);
|
||||
} catch (error) {
|
||||
// noop
|
||||
}
|
||||
|
||||
await this.writeFile(path, blob);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read file content, with utf-8 encoding.
|
||||
*
|
||||
* @param path
|
||||
* @returns
|
||||
*/
|
||||
readFile(path: string): Promise<string> {
|
||||
return fs.promises.readFile(path, 'utf8');
|
||||
}
|
||||
|
||||
async readBinaryFile(path: string): Promise<Buffer> {
|
||||
const content = await fs.promises.readFile(path, 'binary');
|
||||
return Buffer.from(content, 'binary');
|
||||
}
|
||||
|
||||
removeFile(path: string): Promise<void> {
|
||||
return fs.promises.unlink(path);
|
||||
}
|
||||
|
||||
renameFile(oldPath: string, newPath: string): Promise<void> {
|
||||
return fs.promises.rename(oldPath, newPath);
|
||||
}
|
||||
|
||||
copyFile(from: string, to: string): Promise<void> {
|
||||
return fs.promises.copyFile(from, to);
|
||||
}
|
||||
|
||||
copyFolder(from: string, to: string): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
fse.copy(from, to, { recursive: true }, (err) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a JSON file, with utf-8 encoding.
|
||||
*
|
||||
* @param path
|
||||
* @returns
|
||||
*/
|
||||
async readJson<T = any>(path: string): Promise<T> {
|
||||
const fileContent = await fs.promises.readFile(path, 'utf8');
|
||||
return JSON.parse(fileContent) as T;
|
||||
}
|
||||
|
||||
async writeJson(path: string, obj: any): Promise<void> {
|
||||
const tmpFileName = path + '.tmp-' + Date.now();
|
||||
|
||||
let jsonText = '';
|
||||
|
||||
try {
|
||||
jsonText = JSON.stringify(obj);
|
||||
} catch (error) {
|
||||
console.log('Error serializing json', error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
try {
|
||||
await fs.promises.writeFile(tmpFileName, jsonText);
|
||||
await fs.promises.rename(tmpFileName, path);
|
||||
} catch (error) {
|
||||
await fs.promises.unlink(tmpFileName);
|
||||
console.log('Error writing json file', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the folder is empty.
|
||||
*
|
||||
* @param path
|
||||
* @returns Returns true, if the folder is empty; Otherwise, false.
|
||||
*/
|
||||
async isDirectoryEmpty(path: string): Promise<boolean> {
|
||||
const files = await this.listDirectory(path);
|
||||
return files.length === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* List all entries in the directory.
|
||||
*
|
||||
* @param path
|
||||
* @returns A list of all entries.
|
||||
*/
|
||||
async listDirectory(path: string): Promise<FileInfo[]> {
|
||||
const files = await fs.promises.readdir(path);
|
||||
return files.map(function (f) {
|
||||
return {
|
||||
fullPath: path + '/' + f,
|
||||
name: f,
|
||||
isDirectory: fs.lstatSync(path + '/' + f).isDirectory()
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all the files including all sub folders.
|
||||
*
|
||||
* @param path
|
||||
* @returns
|
||||
*/
|
||||
listDirectoryFiles(path: string): Promise<FileInfo[]> {
|
||||
// https://stackoverflow.com/a/5827895
|
||||
const walk = function (dir: string, done: (error: unknown, results?: string[]) => void) {
|
||||
let results = [];
|
||||
fs.readdir(dir, function (err, list) {
|
||||
if (err) return done(err);
|
||||
let pending = list.length;
|
||||
if (!pending) return done(null, results);
|
||||
list.forEach(function (file) {
|
||||
file = nodePath.resolve(dir, file);
|
||||
fs.stat(file, function (err, stat) {
|
||||
if (stat && stat.isDirectory()) {
|
||||
walk(file, function (err, res) {
|
||||
results = results.concat(res);
|
||||
if (!--pending) done(null, results);
|
||||
});
|
||||
} else {
|
||||
results.push(file);
|
||||
if (!--pending) done(null, results);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return new Promise<FileInfo[]>((resolve, reject) => {
|
||||
walk(path, function (error, files) {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(
|
||||
files.map(function (fullPath) {
|
||||
const isDirectory = (function () {
|
||||
try {
|
||||
return fs.lstatSync(fullPath).isDirectory();
|
||||
} catch (_err) {
|
||||
return false;
|
||||
}
|
||||
})();
|
||||
|
||||
return {
|
||||
fullPath,
|
||||
name: nodePath.basename(fullPath),
|
||||
isDirectory
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* https://github.com/jprichardson/node-fs-extra/blob/HEAD/docs/ensureDir.md
|
||||
* @param path
|
||||
* @returns
|
||||
*/
|
||||
makeDirectory(path: string): Promise<void> {
|
||||
if (path.length === 0 || fs.existsSync(path)) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
mkdirp(path, function (err) {
|
||||
if (err) reject({ result: 'failure', err: err });
|
||||
else resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
removeDirRecursive(path: string): void {
|
||||
fse.removeSync(path);
|
||||
}
|
||||
|
||||
openDialog(args: OpenDialogOptions): Promise<string> {
|
||||
throw new Error('Not Supported');
|
||||
}
|
||||
|
||||
unzipUrl(url: string, to: string): Promise<void> {
|
||||
const _this = this;
|
||||
|
||||
function unzipToFolder(path: string, blob: any, callback: (_: { result: 'success' | 'failure' }) => void) {
|
||||
JSZip.loadAsync(blob)
|
||||
.then(function (zip) {
|
||||
let numFiles = Object.keys(zip.files).length;
|
||||
let err = false;
|
||||
function fileCompleted(_success?: boolean) {
|
||||
numFiles--;
|
||||
if (numFiles === 0) {
|
||||
if (err) callback({ result: 'failure' });
|
||||
else callback({ result: 'success' });
|
||||
}
|
||||
}
|
||||
|
||||
Object.keys(zip.files).forEach(function (filename) {
|
||||
if (zip.files[filename].dir) {
|
||||
fileCompleted();
|
||||
return;
|
||||
} // Ignore dirs
|
||||
|
||||
let dest, buffer;
|
||||
|
||||
zip
|
||||
.file(filename)
|
||||
.async('nodebuffer')
|
||||
.then((_buffer) => {
|
||||
dest = nodePath.join(path, filename);
|
||||
buffer = _buffer;
|
||||
return _this.makeDirectory(nodePath.dirname(dest));
|
||||
})
|
||||
.then(() => {
|
||||
fs.writeFileSync(dest, buffer);
|
||||
fileCompleted();
|
||||
})
|
||||
.catch((e) => {
|
||||
err = e;
|
||||
fileCompleted(false);
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch(function (e) {
|
||||
callback({ result: 'failure' });
|
||||
});
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
// Make sure the folder is empty
|
||||
const isEmpty = this.isDirectoryEmpty(to);
|
||||
if (!isEmpty) {
|
||||
reject({ result: 'failure', message: 'Folder must be empty' });
|
||||
return;
|
||||
}
|
||||
|
||||
// Load zip file from URL
|
||||
// @ts-ignore XMLHttpRequest
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', url, true);
|
||||
xhr.responseType = 'blob';
|
||||
xhr.onload = function (_e) {
|
||||
unzipToFolder(to, this.response, function (r) {
|
||||
if (r.result !== 'success') {
|
||||
reject({ result: 'failure', message: 'Failed to extract' });
|
||||
_this.removeDirRecursive(to);
|
||||
return;
|
||||
}
|
||||
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
xhr.send();
|
||||
});
|
||||
}
|
||||
|
||||
makeUniquePath(path: string): string {
|
||||
let _path = path;
|
||||
let count = 1;
|
||||
while (fs.existsSync(_path)) {
|
||||
_path = path + '-' + count;
|
||||
count++;
|
||||
}
|
||||
return _path;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user