You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
vizality/renderer/src/modules/util/File.js

242 lines
6.5 KiB

/* eslint-disable no-unused-vars */
/**
* Contains functions relating to files.
* @module File
* @memberof Util
* @namespace Util.File
*/
import { promises, existsSync, lstatSync, readFileSync, readdirSync } from 'fs';
import { lookup as _getMimeType } from 'mime-types';
import { extname, join, parse } from 'path';
import { escapeRegExp } from 'lodash';
import { get } from '@vizality/http';
import imageSize from 'image-size';
import { promisify } from 'util';
import { log, warn, error } from './Logger';
import { Directories } from '../constants';
import { isString } from './String';
const { readdir, lstat, unlink, rmdir } = promises;
const _getImageSize = promisify(imageSize);
/**
* @private
*/
const _labels = [ 'Util', 'File' ];
const _log = (labels, ...message) => log({ labels, message });
const _warn = (labels, ...message) => warn({ labels, message });
const _error = (labels, ...message) => error({ labels, message });
export const getCaller = path => {
try {
if (path) {
const plugin = path.match(new RegExp(`${escapeRegExp(Directories.PLUGINS)}.([-\\w]+)`));
if (plugin) {
return {
type: 'plugin',
id: plugin[1]
};
}
const builtin = path.match(new RegExp(`${escapeRegExp(Directories.BUILTINS)}.([-\\w]+)`));
if (builtin) {
return {
type: 'builtin',
id: builtin[1]
};
}
const theme = path.match(new RegExp(`${escapeRegExp(Directories.THEMES)}.([-\\w]+)`));
if (theme) {
return {
type: 'theme',
id: theme[1]
};
}
return {
type: 'core',
id: 'vizality'
};
}
/**
* If no path is provided, try to determine one with a forced error stack trace.
*/
const stackTrace = (new Error()).stack;
const plugin = stackTrace.match(new RegExp(`${escapeRegExp(Directories.PLUGINS)}.([-\\w]+)`));
if (plugin) {
return {
type: 'plugin',
id: plugin[1]
};
}
const builtin = stackTrace.match(new RegExp(`${escapeRegExp(Directories.BUILTINS)}.([-\\w]+)`));
if (builtin) {
return {
type: 'builtin',
id: builtin[1]
};
}
const theme = stackTrace.match(new RegExp(`${escapeRegExp(Directories.THEMES)}.([-\\w]+)`));
if (theme) {
return {
type: 'theme',
id: theme[1]
};
}
return {
type: 'core',
id: 'vizality'
};
} catch (err) {
_error(_labels.concat('getCaller'), err);
}
};
export const getMimeType = async input => {
try {
let type = null;
type = _getMimeType(input);
if (!type) {
const response = await get(input);
const blob = await response?.blob?.();
if (!blob) {
type = response?.headers?.['content-type']?.split(';')[0];
} else {
type = blob?.type;
}
}
if (!type) {
if (typeof input !== 'string') {
return type;
}
const mimeType = input.match(/data:([a-zA-Z0-9]+\/[a-zA-Z0-9-.+]+).*,.*/);
if (mimeType && mimeType.length) [ , type ] = mimeType;
}
return type;
} catch (err) {
_error(_labels.concat('getMimeType'), err);
}
};
export const removeDirRecursive = async directory => {
if (existsSync(directory)) {
const files = await readdir(directory);
for (const file of files) {
const currentPath = `${directory}/${file}`;
const stat = await lstat(currentPath);
if (stat.isDirectory()) {
await this.removeDirRecursive(currentPath);
} else {
await unlink(currentPath);
}
}
await rmdir(directory);
}
};
/**
* Gets the dimensions of an image or video. Works for URLs (http/blob/data/protocol).
*/
export const getMediaDimensions = async (url, mimeType) => {
mimeType = mimeType || await this.getMimeType(url);
// Check if it's an image
if (mimeType?.split('/')[0] === 'image') {
// If it's a file, we'll use the image-size package
if (existsSync(url) && lstatSync(url).isFile()) {
return new Promise(resolved => {
_getImageSize(url).then(dimensions => resolved({ width: dimensions.width, height: dimensions.height }));
});
}
return new Promise(resolved => {
const img = new Image();
img.onload = () => resolved({ width: img.naturalWidth, height: img.naturalHeight });
img.src = url;
});
// Check if it's a video
} else if (mimeType?.split('/')[0] === 'video') {
return new Promise(resolve => {
const video = document.createElement('video');
video.src = url;
video.addEventListener('loadedmetadata', () =>
resolve({
width: video.videoWidth,
height: video.videoHeight
})
);
});
}
};
export const convertUrlToFile = (url, fileName) => {
return get(url)
.then(res => res.arrayBuffer())
.then(async buffer => new File([ buffer ], fileName, { type: await this.getMimeType(url) }));
};
export const getObjectURL = async (path, allowedExtensions = [ '.png', '.jpg', '.jpeg', '.webp', '.gif' ]) => {
if (isString(allowedExtensions) && allowedExtensions !== 'all') {
allowedExtensions = [ allowedExtensions ];
}
const urlObjects = [];
const isDir = existsSync(path) && lstatSync(path).isDirectory();
const isFile = existsSync(path) && lstatSync(path).isFile();
const getURL = async file => {
const buffer = readFileSync(file);
const ext = extname(file).slice(1);
const type = await this.getMimeType(ext);
const blob = new Blob([ buffer ], { type });
const url = URL.createObjectURL(blob);
const { name } = parse(file);
/**
* If it's an image, let's include the width and height
* as properties to make it easier on the developer.
*/
let width, height;
if ([ 'png', 'jpg', 'jpeg', 'webp', 'gif' ].includes(ext)) {
const dimensions = await this.getMediaDimensions(url, type);
({ width, height } = dimensions);
}
if (width && height) {
return urlObjects.push({
name,
url,
path: file,
width,
height,
type
});
}
return urlObjects.push({
name,
url,
path: file,
type
});
};
if (isDir) {
const files = readdirSync(path)
.filter(file => lstatSync(join(path, file)).isFile() && (allowedExtensions.indexOf(extname(file)) !== -1 || allowedExtensions === 'all'));
for (const file of files) {
await getURL(join(path, file));
}
} else {
if (isFile) {
await getURL(path);
}
}
return urlObjects;
};