mirror of https://github.com/vizality/vizality
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.
242 lines
6.5 KiB
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;
|
|
};
|